From 251ba74dbe76aca1b5768e65e3efbde24afa5138 Mon Sep 17 00:00:00 2001 From: Sangbum Kim Date: Wed, 6 Jun 2018 15:30:23 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9C=A0=ED=8B=B8=ED=81=B4=EB=A0=88=EC=8A=A4?= =?UTF-8?q?=20=EC=9E=94=EB=9C=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- buf/bytebuf_pool.go | 88 ++++++++++++++ buf/bytes_queue.go | 41 +++++++ buf/string_queue.go | 42 +++++++ handler/handler.go | 48 ++++++++ http/client/client.go | 103 ++++++++++++++++ http/client/round_tripper.go | 35 ++++++ http/cookie.go | 26 ++++ http/middleware/accesslog.go | 136 +++++++++++++++++++++ http/resp_message.go | 55 +++++++++ http/response.go | 182 ++++++++++++++++++++++++++++ http/tcp_listener.go | 33 +++++ io/reader.go | 17 +++ io/reader_writer.go | 25 ++++ io/remover.go | 80 +++++++++++++ io/writer.go | 17 +++ logger/common.go | 14 +++ logger/rotater/global.go | 52 ++++++++ logger/rotater/set_logcore.go | 47 ++++++++ logger/rotater/wrapped.go | 39 ++++++ logger/rotater/writeRotater.go | 51 ++++++++ logger/zap/log_format.go | 17 +++ logger/zap/logger.go | 105 ++++++++++++++++ logger/zap/wrapped.go | 14 +++ misc/converse.go | 126 ++++++++++++++++++++ misc/dumper.go | 112 +++++++++++++++++ misc/uuid.go | 118 ++++++++++++++++++ net/ipv4_resolver.go | 41 +++++++ net/ipv4_resolver_test.go | 37 ++++++ net/local.go | 36 ++++++ tty/code/termcodes.go | 212 +++++++++++++++++++++++++++++++++ tty/code/termcodes_darwin.go | 45 +++++++ tty/code/termcodes_linux.go | 45 +++++++ tty/term.go | 114 ++++++++++++++++++ 33 files changed, 2153 insertions(+) create mode 100644 buf/bytebuf_pool.go create mode 100644 buf/bytes_queue.go create mode 100644 buf/string_queue.go create mode 100644 handler/handler.go create mode 100644 http/client/client.go create mode 100644 http/client/round_tripper.go create mode 100644 http/cookie.go create mode 100644 http/middleware/accesslog.go create mode 100644 http/resp_message.go create mode 100644 http/response.go create mode 100644 http/tcp_listener.go create mode 100644 io/reader.go create mode 100644 io/reader_writer.go create mode 100644 io/remover.go create mode 100644 io/writer.go create mode 100644 logger/common.go create mode 100644 logger/rotater/global.go create mode 100644 logger/rotater/set_logcore.go create mode 100644 logger/rotater/wrapped.go create mode 100644 logger/rotater/writeRotater.go create mode 100644 logger/zap/log_format.go create mode 100644 logger/zap/logger.go create mode 100644 logger/zap/wrapped.go create mode 100644 misc/converse.go create mode 100644 misc/dumper.go create mode 100644 misc/uuid.go create mode 100644 net/ipv4_resolver.go create mode 100644 net/ipv4_resolver_test.go create mode 100644 net/local.go create mode 100644 tty/code/termcodes.go create mode 100644 tty/code/termcodes_darwin.go create mode 100644 tty/code/termcodes_linux.go create mode 100644 tty/term.go diff --git a/buf/bytebuf_pool.go b/buf/bytebuf_pool.go new file mode 100644 index 0000000..2a2a0b4 --- /dev/null +++ b/buf/bytebuf_pool.go @@ -0,0 +1,88 @@ +package buf + +import ( + "sync" + "unicode/utf8" +) + +const ( + defaultByteBufferSize = 4096 +) + +// ByteBuffer provides byte buffer, which can be used with fasthttp API +// in order to minimize memory allocations. +// +// ByteBuffer may be used with functions appending data to the given []byte +// slice. See example code for details. +// +// Use AcquireByteBuffer for obtaining an empty byte buffer. +type ByteBuffer struct { + // B is a byte buffer to use in append-like workloads. + // See example code for details. + B []byte +} + +func (b *ByteBuffer) WriteByte(c byte) error { + b.B = append(b.B, c) + return nil +} + +// Write implements io.Writer - it appends p to ByteBuffer.B +func (b *ByteBuffer) Write(p []byte) (int, error) { + b.B = append(b.B, p...) + return len(p), nil +} + +// WriteString appends s to ByteBuffer.B +func (b *ByteBuffer) WriteString(s string) (int, error) { + b.B = append(b.B, s...) + return len(s), nil +} + +//(r rune) (n int, err error) { +// WriteString appends s to ByteBuffer.B +func (b *ByteBuffer) WriteRune(r rune) (n int, err error) { + if r < utf8.RuneSelf { + b.B = append(b.B, byte(r)) + return 1, nil + } + curSize := len(b.B) + runCharBuf := b.B[curSize:curSize+utf8.UTFMax] + n = utf8.EncodeRune(runCharBuf, r) + b.B = b.B[:curSize+n] + return n, nil +} + +// Set sets ByteBuffer.B to p +func (b *ByteBuffer) Set(p []byte) { + b.B = append(b.B[:0], p...) +} + +// SetString sets ByteBuffer.B to s +func (b *ByteBuffer) SetString(s string) { + b.B = append(b.B[:0], s...) +} + +// Reset makes ByteBuffer.B empty. +func (b *ByteBuffer) Reset() { + b.B = b.B[:0] +} + +type ByteBufferPool struct { + pool sync.Pool +} + +func (p *ByteBufferPool) Acquire() *ByteBuffer { + v := p.pool.Get() + if v == nil { + return &ByteBuffer{ + B: make([]byte, 0, defaultByteBufferSize), + } + } + return v.(*ByteBuffer) +} + +func (p *ByteBufferPool) Release(b *ByteBuffer) { + b.Reset() + p.pool.Put(b) +} diff --git a/buf/bytes_queue.go b/buf/bytes_queue.go new file mode 100644 index 0000000..1cd557e --- /dev/null +++ b/buf/bytes_queue.go @@ -0,0 +1,41 @@ +package buf + +import ( + "container/list" +) + +func NewBytesQueue() (chan<- []byte, <-chan []byte) { + send := make(chan []byte, 1) + receive := make(chan []byte, 1) + go manageBytesQueue(send, receive) + return send, receive +} + +func manageBytesQueue(send <-chan []byte, receive chan<- []byte) { + queue := list.New() + for { + if front := queue.Front(); front == nil { + if send == nil { + close(receive) + return + } + value, ok := <-send + if !ok { + close(receive) + return + } + queue.PushBack(value) + } else { + select { + case receive <- front.Value.([]byte): + queue.Remove(front) + case value, ok := <-send: + if ok { + queue.PushBack(value) + } else { + send = nil + } + } + } + } +} diff --git a/buf/string_queue.go b/buf/string_queue.go new file mode 100644 index 0000000..1f6366b --- /dev/null +++ b/buf/string_queue.go @@ -0,0 +1,42 @@ +package buf + +import ( + "container/list" +) + +// 블럭되지 않는 큐체널 +func NewStringQueue() (chan<- string, <-chan string) { + send := make(chan string, 1) + receive := make(chan string, 1) + go manageStringQueue(send, receive) + return send, receive +} + +func manageStringQueue(send <-chan string, receive chan<- string) { + queue := list.New() + for { + if front := queue.Front(); front == nil { + if send == nil { + close(receive) + return + } + value, ok := <-send + if !ok { + close(receive) + return + } + queue.PushBack(value) + } else { + select { + case receive <- front.Value.(string): + queue.Remove(front) + case value, ok := <-send: + if ok { + queue.PushBack(value) + } else { + send = nil + } + } + } + } +} diff --git a/handler/handler.go b/handler/handler.go new file mode 100644 index 0000000..56c53fb --- /dev/null +++ b/handler/handler.go @@ -0,0 +1,48 @@ +package util + +import ( + "sync" + "context" + "log" +) + +// 자식들을 기다리는 context waiter +type Handler struct { + errorChan chan error + ctx context.Context + canceler context.CancelFunc + waiter *sync.WaitGroup +} + +func NewHandler(ctx context.Context) *Handler { + ctx, canceler := context.WithCancel(ctx) + return &Handler{ + ctx: ctx, + canceler: canceler, + waiter: &sync.WaitGroup{}, + errorChan: make(chan error, 5), + } +} + +func (h *Handler) NotifyError(err error) { h.errorChan <- err } +func (h *Handler) Error() <-chan error { return h.errorChan } +func (h *Handler) Done() <-chan struct{} { return h.ctx.Done() } +func (h *Handler) GracefulWait() { + if h.ctx.Err() == nil { + h.canceler() + } + + h.waiter.Wait() + close(h.errorChan) + + for remainError := range h.errorChan { + log.Println("remain errors ", remainError) + } +} + +func (h *Handler) IncreaseWait() { + h.waiter.Add(1) +} +func (h *Handler) DecreaseWait() { + h.waiter.Done() +} diff --git a/http/client/client.go b/http/client/client.go new file mode 100644 index 0000000..99e4b94 --- /dev/null +++ b/http/client/client.go @@ -0,0 +1,103 @@ +package client + +import ( + "net/http" + "net" + "time" + "errors" + "net/url" + "io" +) + +var ( + httpCannotRedirectError = errors.New("this client cannot redirect") + + disableRedirect = func(_ *http.Request, _ []*http.Request) error { + return httpCannotRedirectError + } + limitedRedirect = func(_ *http.Request, via []*http.Request) error { + if len(via) >= 10 { + return errors.New("stopped after 10 redirects") + } + return nil + } +) + +type GracefulClient interface { + http.RoundTripper + client() *http.Client + roundTripper() http.RoundTripper + Do(req *http.Request) (*http.Response, error) + Get(url string) (resp *http.Response, err error) + Head(url string) (resp *http.Response, err error) + Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) + PostForm(url string, data url.Values) (resp *http.Response, err error) +} + + +type wrappedClient struct { + http.Client +} + +func (cli *wrappedClient) client() (*http.Client) { + return &cli.Client +} +func (cli *wrappedClient) roundTripper() http.RoundTripper { + return cli.Transport +} + +func (cli *wrappedClient) RoundTrip(req *http.Request) (*http.Response, error) { + return cli.Client.Transport.RoundTrip(req) +} + +func NewClient( + keepaliveDuration time.Duration, + connectTimeout time.Duration, + responseHeaderTimeout time.Duration, + idleConnectionTimeout time.Duration, + maxIdleConnections int, + redirectSupport bool, + serverName string, +) GracefulClient { + + srvName := []string{serverName} + var redirectChecker func(*http.Request, []*http.Request) error + if redirectSupport { + redirectChecker = limitedRedirect + } else { + redirectChecker = disableRedirect + } + + keepaliveDisabled := keepaliveDuration == 0 + dialer := &net.Dialer{ + Timeout: connectTimeout, + KeepAlive: keepaliveDuration, + DualStack: false, + } + + transport := &predefinedHeaderTransport{ + useragentName: srvName, + Transport: http.Transport{ + Proxy: nil, + DialTLS: nil, + TLSClientConfig: nil, + DisableKeepAlives: keepaliveDisabled, + DisableCompression: true, + MaxIdleConnsPerHost: maxIdleConnections, + DialContext: dialer.DialContext, + MaxIdleConns: maxIdleConnections, + IdleConnTimeout: idleConnectionTimeout, + ResponseHeaderTimeout: responseHeaderTimeout, + TLSNextProto: nil, + ExpectContinueTimeout: 0, + }, + } + + return &wrappedClient{ + Client: http.Client{ + Transport: transport, + CheckRedirect: redirectChecker, + Jar: nil, + }, + } +} diff --git a/http/client/round_tripper.go b/http/client/round_tripper.go new file mode 100644 index 0000000..612d96d --- /dev/null +++ b/http/client/round_tripper.go @@ -0,0 +1,35 @@ +package client + +import ( + "net/http" +) + +const ( + connectionHeaderKey = "Connection" + connectionUserAgentHeaderKey = "User-Agent" + connectionCloseHeader = "close" + connectionKeepAliveHeader = "keep-alive" +) + +var ( + connectionCloseHeaderValue = []string{connectionCloseHeader} + connectionKeepAliveHeaderValue = []string{connectionKeepAliveHeader} +) + +type predefinedHeaderTransport struct { + useragentName []string + http.Transport +} + +func (pht *predefinedHeaderTransport) RoundTrip(req *http.Request) (res *http.Response, err error) { + var connectionValue []string + if pht.DisableKeepAlives { + connectionValue = connectionCloseHeaderValue + } else { + connectionValue = connectionKeepAliveHeaderValue + } + req.Header[connectionHeaderKey] = connectionValue + req.Header[connectionUserAgentHeaderKey] = pht.useragentName + res, err = pht.Transport.RoundTrip(req) + return +} diff --git a/http/cookie.go b/http/cookie.go new file mode 100644 index 0000000..374ce52 --- /dev/null +++ b/http/cookie.go @@ -0,0 +1,26 @@ +package http + +import ( + "time" + "net/http" +) + +func SetCookieValue(w http.ResponseWriter, host, key, newCookieValue string) { + http.SetCookie(w, + &http.Cookie{ + Name: key, + Value: newCookieValue, + Path: "/", + Domain: host, + Expires: time.Now().UTC().AddDate(3, 0, 0), + Secure: true, + HttpOnly: true, + }, + ) +} +func GetCookieValue(req *http.Request, name string) (cookieValue string) { + if cookie, _ := req.Cookie(name); cookie != nil { + cookieValue = cookie.Value + } + return +} diff --git a/http/middleware/accesslog.go b/http/middleware/accesslog.go new file mode 100644 index 0000000..7e87efc --- /dev/null +++ b/http/middleware/accesslog.go @@ -0,0 +1,136 @@ +package middleware + +import ( + "net" + "net/http" + "time" + "sync" + myhttp "amuz.es/src/infra/goutils/http" + "amuz.es/src/infra/goutils/buf" + "amuz.es/src/infra/goutils/misc" + "io" +) + +const ( + dateFormat = "02/Jan/2006:15:04:05 -0700" + forwardedForIPHeader = "X-Forwarded-For" +) + +type bufferedWriter interface { + WriteTo(w io.Writer) (n int64, err error) + Write(p []byte) (n int, err error) +} + +type accessLoggerMiddleware struct { + serverNameValue []string + writer io.Writer + waiter *sync.WaitGroup + dumpQ bufferedWriter + writerFlushChan chan bool +} + +func (m *accessLoggerMiddleware) Handle(next http.Handler) http.Handler { + accessLoggerBufPool := buf.ByteBufferPool{} + fn := func(w http.ResponseWriter, r *http.Request) { + m.waiter.Add(1) + respWrapper := myhttp.NewWrapResponseWriter(w, r.ProtoMajor) + defer func(now time.Time) { + dur := time.Since(now) + buf := accessLoggerBufPool.Acquire() + defer accessLoggerBufPool.Release(buf) + buf.WriteString(m.remoteHost(r)) + buf.WriteString(` - - [`) + buf.WriteString(now.Format(dateFormat)) + buf.WriteString(`] "`) + buf.WriteString(r.Method) + buf.WriteByte(' ') + buf.WriteString(r.RequestURI) + buf.WriteByte(' ') + buf.WriteString(r.Proto) + buf.WriteString(`" `) + buf.WriteString(misc.FormatInt(respWrapper.Status())) + buf.WriteByte(' ') + buf.WriteString(misc.FormatInt(respWrapper.BytesWritten())) + buf.WriteString(` "`) + buf.WriteString(r.Referer()) + buf.WriteString(`" "`) + buf.WriteString(r.UserAgent()) + buf.WriteString(`" `) + buf.WriteString(misc.FormatInt64(dur.Nanoseconds() / time.Millisecond.Nanoseconds())) + buf.WriteByte(' ') + buf.WriteString(r.Host) + buf.WriteByte('\n') + m.dumpQ.Write(buf.B) + m.waiter.Done() + }(time.Now().Local()) + + respWrapper.Header()["Server"] = m.serverNameValue + + next.ServeHTTP(respWrapper, r) + } + return http.HandlerFunc(fn) +} +func (m *accessLoggerMiddleware) Close() { + m.waiter.Wait() + m.writerFlushChan <- true + <-m.writerFlushChan +} + +func (m *accessLoggerMiddleware) lineByLineWriter() { + ticker := time.NewTicker(250 * time.Millisecond) + defer func() { + ticker.Stop() + m.dumpQ.WriteTo(m.writer) + close(m.writerFlushChan) + }() + for { + <-ticker.C + m.dumpQ.WriteTo(m.writer) + select { + case <-m.writerFlushChan: + return + default: + } + } +} + +// strip port from addresses with hostname, ipv4 or ipv6 +func (m *accessLoggerMiddleware) stripPort(address string) string { + if h, _, err := net.SplitHostPort(address); err == nil { + return h + } + + return address +} + +// The remote address of the client. When the 'X-Forwarded-For' +// header is set, then it is used instead. +func (m *accessLoggerMiddleware) remoteAddr(r *http.Request) string { + ff := r.Header.Get(forwardedForIPHeader) + if ff != "" { + return ff + } + + return r.RemoteAddr +} + +func (m *accessLoggerMiddleware) remoteHost(r *http.Request) string { + a := m.remoteAddr(r) + h := m.stripPort(a) + if h != "" { + return h + } + + return "-" +} + +func AccessLog(serverName string, bufferingWriter bufferedWriter, accessLogWriter io.Writer) (func(next http.Handler) http.Handler, func()) { + impl := &accessLoggerMiddleware{} + impl.serverNameValue = append(impl.serverNameValue, serverName) + impl.writer = accessLogWriter + impl.waiter = &sync.WaitGroup{} + impl.writerFlushChan = make(chan bool, 1) + impl.dumpQ = bufferingWriter + go impl.lineByLineWriter() + return impl.Handle, impl.Close +} diff --git a/http/resp_message.go b/http/resp_message.go new file mode 100644 index 0000000..2ae4872 --- /dev/null +++ b/http/resp_message.go @@ -0,0 +1,55 @@ +package http + +const ( + // 200 + JsonOK ResponseMessage = "{\"message\":\"OK\"}" + JsonPartialContent = "{\"message\":\"Partial Content\"}" + + // 300 + JsonMovedPermanently ResponseMessage = "{\"message\":\"Moved Permanently\"}" + JsonFound = "{\"message\":\"Found\"}" + JsonSeeOther = "{\"message\":\"See Other\"}" + JsonTemporaryRedirect = "{\"message\":\"Temporary Redirect\"}" + JsonPermanentRedirect = "{\"message\":\"Permanent Redirect\"}" + JsonUnauthorized = "{\"message\":\"Unauthorized\"}" + JsonPaymentRequired = "{\"message\":\"Payment Required\"}" + + // 400 + JsonBadRequest ResponseMessage = "{\"message\":\"Bad Request\"}" + JsonForbidden = "{\"message\":\"Forbidden\"}" + JsonNotFound = "{\"message\":\"Not Found\"}" + JsonMethodNotAllowed = "{\"message\":\"Method Not Allowed\"}" + JsonNotAcceptable = "{\"message\":\"Not Acceptable\"}" + JsonRequestTimeout = "{\"message\":\"Request Timeout\"}" + JsonConflict = "{\"message\":\"Conflict\"}" + JsonGone = "{\"message\":\"Gone\"}" + JsonPreconditionFailed = "{\"message\":\"Precondition Failed\"}" + JsonRequestEntityTooLarge = "{\"message\":\"Request Entity Too Large\"}" + JsonRequestURITooLong = "{\"message\":\"Request URI Too Long\"}" + JsonUnsupportedMediaType = "{\"message\":\"Unsupported Media Type\"}" + JsonUpgradeRequired = "{\"message\":\"Upgrade Required\"}" + JsonPreconditionRequired = "{\"message\":\"Precondition Required\"}" + JsonTooManyRequests = "{\"message\":\"Too Many Requests\"}" + JsonRequestHeaderFieldsTooLarge = "{\"message\":\"Request Header Fields Too Large\"}" + JsonUnavailableForLegalReasons = "{\"message\":\"Unavailable For Legal Reasons\"}" + + // 500 + JsonInternalServerError ResponseMessage = "{\"message\":\"Internal Server Error\"}" + JsonNotImplemented = "{\"message\":\"Not Implemented\"}" + JsonBadGateway = "{\"message\":\"Bad Gateway\"}" + JsonServiceUnavailable = "{\"message\":\"Service Unavailable\"}" + JsonGatewayTimeout = "{\"message\":\"Gateway Timeout\"}" + JsonHTTPVersionNotSupported = "{\"message\":\"HTTP Version Not Supported\"}" + JsonInsufficientStorage = "{\"message\":\"Insufficient Storage\"}" + JsonLoopDetected = "{\"message\":\"Loop Detected\"}" +) + +var ( + JsonContentType = []string{"application/json; charset=utf-8"} +) + +type ResponseMessage string + +func (rm ResponseMessage) MarshalJSON() ([]byte, error) { + return []byte(rm), nil +} diff --git a/http/response.go b/http/response.go new file mode 100644 index 0000000..bf1695c --- /dev/null +++ b/http/response.go @@ -0,0 +1,182 @@ +package http + +// The original work was derived from Goji's middleware, source: +// https://github.com/zenazn/goji/tree/master/web/middleware + +import ( + "bufio" + "io" + "net" + "net/http" +) + +// WrapResponseWriter is a proxy around an http.ResponseWriter that allows you to hook +// into various parts of the response process. +type WrapResponseWriter interface { + http.ResponseWriter + // Status returns the HTTP status of the request, or 0 if one has not + // yet been sent. + Status() int + // BytesWritten returns the total number of bytes sent to the client. + BytesWritten() int + // Tee causes the response body to be written to the given io.Writer in + // addition to proxying the writes through. Only one io.Writer can be + // tee'd to at once: setting a second one will overwrite the first. + // Writes will be sent to the proxy before being written to this + // io.Writer. It is illegal for the tee'd writer to be modified + // concurrently with writes. + Tee(io.Writer) + // Unwrap returns the original proxied target. + Unwrap() http.ResponseWriter +} + +// basicWriter wraps a http.ResponseWriter that implements the minimal +// http.ResponseWriter interface. +type basicWriter struct { + http.ResponseWriter + wroteHeader bool + code int + bytes int + tee io.Writer +} + +func (b *basicWriter) WriteHeader(code int) { + if !b.wroteHeader { + b.code = code + b.wroteHeader = true + b.ResponseWriter.WriteHeader(code) + } +} +func (b *basicWriter) Write(buf []byte) (int, error) { + b.WriteHeader(http.StatusOK) + n, err := b.ResponseWriter.Write(buf) + if b.tee != nil { + _, err2 := b.tee.Write(buf[:n]) + // Prefer errors generated by the proxied writer. + if err == nil { + err = err2 + } + } + b.bytes += n + return n, err +} +func (b *basicWriter) maybeWriteHeader() { + if !b.wroteHeader { + b.WriteHeader(http.StatusOK) + } +} +func (b *basicWriter) Status() int { + return b.code +} +func (b *basicWriter) BytesWritten() int { + return b.bytes +} +func (b *basicWriter) Tee(w io.Writer) { + b.tee = w +} +func (b *basicWriter) Unwrap() http.ResponseWriter { + return b.ResponseWriter +} + +type flushWriter struct { + basicWriter +} + +func (f *flushWriter) Flush() { + fl := f.basicWriter.ResponseWriter.(http.Flusher) + fl.Flush() +} + +var _ http.Flusher = &flushWriter{} + +// httpFancyWriter is a HTTP writer that additionally satisfies http.CloseNotifier, +// http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case +// of wrapping the http.ResponseWriter that package http gives you, in order to +// make the proxied object support the full method set of the proxied object. +type httpFancyWriter struct { + basicWriter +} + +func (f *httpFancyWriter) CloseNotify() <-chan bool { + cn := f.basicWriter.ResponseWriter.(http.CloseNotifier) + return cn.CloseNotify() +} +func (f *httpFancyWriter) Flush() { + fl := f.basicWriter.ResponseWriter.(http.Flusher) + fl.Flush() +} +func (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hj := f.basicWriter.ResponseWriter.(http.Hijacker) + return hj.Hijack() +} +func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) { + if f.basicWriter.tee != nil { + n, err := io.Copy(&f.basicWriter, r) + f.basicWriter.bytes += int(n) + return n, err + } + rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) + f.basicWriter.maybeWriteHeader() + n, err := rf.ReadFrom(r) + f.basicWriter.bytes += int(n) + return n, err +} + +var _ http.CloseNotifier = &httpFancyWriter{} +var _ http.Flusher = &httpFancyWriter{} +var _ http.Hijacker = &httpFancyWriter{} +var _ io.ReaderFrom = &httpFancyWriter{} + +// http2FancyWriter is a HTTP2 writer that additionally satisfies http.CloseNotifier, +// http.Flusher, and io.ReaderFrom. It exists for the common case +// of wrapping the http.ResponseWriter that package http gives you, in order to +// make the proxied object support the full method set of the proxied object. +type http2FancyWriter struct { + basicWriter +} + +func (f *http2FancyWriter) CloseNotify() <-chan bool { + cn := f.basicWriter.ResponseWriter.(http.CloseNotifier) + return cn.CloseNotify() +} +func (f *http2FancyWriter) Flush() { + fl := f.basicWriter.ResponseWriter.(http.Flusher) + fl.Flush() +} + +func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error { + return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts) +} + +var _ http.CloseNotifier = &http2FancyWriter{} +var _ http.Flusher = &http2FancyWriter{} +var _ http.Pusher = &http2FancyWriter{} + +// NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to +// hook into various parts of the response process. +func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter { + _, cn := w.(http.CloseNotifier) + _, fl := w.(http.Flusher) + + bw := basicWriter{ResponseWriter: w} + + if protoMajor == 2 { + _, ps := w.(http.Pusher) + if cn && fl && ps { + return &http2FancyWriter{bw} + } + } else { + _, hj := w.(http.Hijacker) + _, rf := w.(io.ReaderFrom) + if cn && fl && hj && rf { + return &httpFancyWriter{bw} + } + } + if fl { + return &flushWriter{bw} + } + + return &bw +} + + diff --git a/http/tcp_listener.go b/http/tcp_listener.go new file mode 100644 index 0000000..26402f7 --- /dev/null +++ b/http/tcp_listener.go @@ -0,0 +1,33 @@ +package http + +import ( + "time" + "net" +) + +func NewListener(addr *net.TCPAddr) (net.Listener, error) { + listener, err := net.ListenTCP("tcp", addr) + if err != nil { + return nil, err + } + + return &keepAliveListener{Listener: listener}, nil + +} + +type keepAliveListener struct { + // the number of open connections + net.Listener +} + +func (ln *keepAliveListener) Accept() (c net.Conn, err error) { + c, err = ln.Listener.Accept() + if err != nil { + return + } + if tc, ok := c.(*net.TCPConn); ok { + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + } + return +} diff --git a/io/reader.go b/io/reader.go new file mode 100644 index 0000000..40fc486 --- /dev/null +++ b/io/reader.go @@ -0,0 +1,17 @@ +package io + +import "io" + +type nopReader bool + +func (nopReader) Read(p []byte) (int, error) { + return 0, io.EOF +} + +func (nopReader) Close() (error) { + return nil +} + +const ( + NopReader nopReader = false +) diff --git a/io/reader_writer.go b/io/reader_writer.go new file mode 100644 index 0000000..c35dcd2 --- /dev/null +++ b/io/reader_writer.go @@ -0,0 +1,25 @@ +package io + +import ( + "io" + "context" +) + +type wrappedIO struct { + stdin io.Reader + stdout io.Writer + closer context.CancelFunc +} + +func (wio *wrappedIO) Read(p []byte) (n int, err error) { + return wio.stdin.Read(p) +} + +func (wio *wrappedIO) Write(p []byte) (n int, err error) { + return wio.stdout.Write(p) +} + +func (wio *wrappedIO) Close() (err error) { + wio.closer() + return io.EOF +} diff --git a/io/remover.go b/io/remover.go new file mode 100644 index 0000000..29a5de9 --- /dev/null +++ b/io/remover.go @@ -0,0 +1,80 @@ +package io + +import ( + "golang.org/x/text/transform" +) + +const ( + linefeed = '\n' + LineFeedRemover linefeedRemover = iota +) + +type linefeedRemover int + +func (linefeedRemover) Reset() {} +func (linefeedRemover) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + cur, start := 0, 0 + for ; cur < len(src); { + if src[cur] == linefeed { + if nDst+1 > len(dst) { + err = transform.ErrShortDst + return + } + nSrc += cur - start + nDst += copy(dst[nDst:], src[start:cur]) + cur++ + start = cur + } else { + cur++ + } + } + if remain := cur - start; remain == 0 { + } else if nDst+1 > len(dst) { + err = transform.ErrShortDst + } else { + nSrc += remain + nDst += copy(dst[nDst:], src[start:cur]) + } + return +} + +type Remover struct { + Removes []byte +} + +func (r *Remover) Reset() {} +func (r *Remover) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + cur, start := 0, 0 + + for ; cur < len(src); { + var ( + curData = src[cur] + matched = false + ) + for i := 0; i < len(r.Removes); i++ { + if matched = r.Removes[i] == curData; matched { + break + } + } + if matched { + if nDst+1 > len(dst) { + err = transform.ErrShortDst + return + } + nSrc += cur - start + nDst += copy(dst[nDst:], src[start:cur]) + cur++ + start = cur + } else { + cur++ + } + } + if remain := cur - start; remain == 0 { + } else if nDst+1 > len(dst) { + err = transform.ErrShortDst + } else { + nSrc += remain + nDst += copy(dst[nDst:], src[start:cur]) + } + return +} diff --git a/io/writer.go b/io/writer.go new file mode 100644 index 0000000..b76ec89 --- /dev/null +++ b/io/writer.go @@ -0,0 +1,17 @@ +package io + +import ( + "io" +) + +type nopWriter struct { + io.Writer +} + +func (nopWriter) Close() error { return nil } + +// NopCloser returns a ReadCloser with a no-op Close method wrapping +// the provided Reader r. +func NopCloser(w io.Writer) io.WriteCloser { + return nopWriter{w} +} diff --git a/logger/common.go b/logger/common.go new file mode 100644 index 0000000..3233676 --- /dev/null +++ b/logger/common.go @@ -0,0 +1,14 @@ +package logger + +import "io" + +type WriteSyncer interface { + io.Writer + Sync() error +} + +type RotateSyncer interface { + WriteSyncer + SetOnClose(func()) + Rotate() error +} diff --git a/logger/rotater/global.go b/logger/rotater/global.go new file mode 100644 index 0000000..1d6e4e5 --- /dev/null +++ b/logger/rotater/global.go @@ -0,0 +1,52 @@ +package rotater + +import ( + "os" + "path" + "log" + "amuz.es/src/infra/goutils/logger" +) + +var loggers logger.RotateSyncerSet + +func NewLogWriter(FileName string, MaxSizeMb, MaxBackup, MaxDay int, logDir string) logger.RotateSyncer { + switch FileName { + case "Stdout": + return newLocked(os.Stdout) + case "Stderr": + return newLocked(os.Stderr) + default: + logpath := FileName + if logDir != "" { + logpath = path.Join(logDir, FileName) + } + + log.Println(" Attention!! log writes to ", logpath) + + logWriter := newRotater( + logpath, + MaxSizeMb, // megabytes + MaxBackup, + MaxDay, //days + ) + loggers.Store(logWriter) + logWriter.SetOnClose(func() { loggers.Delete(logWriter) }) + return logWriter + } +} + +func Rotate() { + loggers.Range(func(rotater logger.RotateSyncer) { + rotater.Sync() + rotater.Rotate() + }) + log.Println("rotated") +} + +func Close() { + loggers.Range(func(rotater logger.RotateSyncer) { + rotater.Sync() + rotater.Close() + }) + log.Println("end of log") +} diff --git a/logger/rotater/set_logcore.go b/logger/rotater/set_logcore.go new file mode 100644 index 0000000..b3ff5e0 --- /dev/null +++ b/logger/rotater/set_logcore.go @@ -0,0 +1,47 @@ +package rotater + +import ( + "sync" + "sync/atomic" + "amuz.es/src/infra/goutils/logger" +) + +/** +logger set + */ +type RotateSyncerSet struct { + storage sync.Map +} + +func (s *RotateSyncerSet) Delete(key logger.RotateSyncer) { + s.storage.Delete(key) +} +func (s *RotateSyncerSet) Exist(key logger.RotateSyncer) (ok bool) { + _, ok = s.storage.Load(key) + return +} +func (s *RotateSyncerSet) SetNx(key logger.RotateSyncer) (bool) { + _, exist := s.storage.LoadOrStore(key, 0) + return !exist +} +func (s *RotateSyncerSet) Range(f func(key logger.RotateSyncer)) { + s.storage.Range(s.rangeWrap(f)) +} +func (s *RotateSyncerSet) Store(key logger.RotateSyncer) { + s.storage.Store(key, 0) +} +func (s *RotateSyncerSet) rangeWrap(f func(key logger.RotateSyncer)) func(key, value interface{}) bool { + ok := true + return func(key, value interface{}) bool { + f(key.(logger.RotateSyncer)) + return ok + } +} + +func (s *RotateSyncerSet) Len() int { + var count uint64 + s.Range(func(conn logger.RotateSyncer) { + atomic.AddUint64(&count, 1) + }) + return int(count) +} diff --git a/logger/rotater/wrapped.go b/logger/rotater/wrapped.go new file mode 100644 index 0000000..0ae175e --- /dev/null +++ b/logger/rotater/wrapped.go @@ -0,0 +1,39 @@ +package rotater + +import ( + "sync" + "amuz.es/src/infra/goutils/logger" +) + +type LockedWriteSyncer struct { + sync.Mutex + ws logger.WriteSyncer +} + +// Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In +// particular, *os.Files must be locked before use. +func newLocked(ws logger.WriteSyncer) logger.RotateSyncer { + if lws, ok := ws.(*LockedWriteSyncer); ok { + // no need to layer on another lock + return lws + } + return &LockedWriteSyncer{ws: ws} +} + +func (s *LockedWriteSyncer) Write(bs []byte) (int, error) { + s.Lock() + n, err := s.ws.Write(bs) + s.Unlock() + return n, err +} + +func (s *LockedWriteSyncer) Sync() error { + s.Lock() + err := s.ws.Sync() + s.Unlock() + return err +} + +func (r *LockedWriteSyncer) SetOnClose(closeFunc logger.CloseFunc) {} +func (r *LockedWriteSyncer) Rotate() (err error) { return } +func (r *LockedWriteSyncer) Close() (err error) { return } diff --git a/logger/rotater/writeRotater.go b/logger/rotater/writeRotater.go new file mode 100644 index 0000000..9826c03 --- /dev/null +++ b/logger/rotater/writeRotater.go @@ -0,0 +1,51 @@ +package rotater + +import ( + "sync" + "amuz.es/src/infra/goutils/logger" +) + +type rotateSyncer struct { + setOnceOnclose *sync.Once + onClose func() + lumberjack.Logger +} + +func newRotater(filename string, maxSize, maxBackup, maxDay int) logger.RotateSyncer { + return &rotateSyncer{ + setOnceOnclose: &sync.Once{}, + Logger: lumberjack.Logger{ + Filename: filename, + MaxSize: maxSize, // megabytes + MaxBackups: maxBackup, + MaxAge: maxDay, //days + LocalTime: false, + Compress: false, + }, + } +} +func (r *rotateSyncer) SetOnClose(closeFunc func()) { + r.setOnceOnclose.Do(func() { + r.onClose = closeFunc + }) +} + +func (r *rotateSyncer) Rotate() error { + return r.Logger.Rotate() +} +func (r *rotateSyncer) Close() error { + defer func() { + if r.onClose != nil { + r.onClose() + } + }() + return r.Logger.Close() +} + +func (r *rotateSyncer) Sync() error { + return nil +} + +func (s *rotateSyncer) Write(bs []byte) (int, error) { + return s.Logger.Write(bs) +} diff --git a/logger/zap/log_format.go b/logger/zap/log_format.go new file mode 100644 index 0000000..69feccc --- /dev/null +++ b/logger/zap/log_format.go @@ -0,0 +1,17 @@ +package zap + +import "go.uber.org/zap/zapcore" + +var LogCommonFormat = zapcore.EncoderConfig{ + TimeKey: "ts", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, +} diff --git a/logger/zap/logger.go b/logger/zap/logger.go new file mode 100644 index 0000000..3cef9ee --- /dev/null +++ b/logger/zap/logger.go @@ -0,0 +1,105 @@ +package zap + +import ( + "go.uber.org/zap" + + "go.uber.org/zap/zapcore" + "amuz.es/src/infra/goutils/logger" +) + +var ( + defaultWriter logger.RotateSyncer + defaultErrorOutputOptions []zap.Option + nopCloser = func() (err error) { return } +) + +func init() { + zap.RedirectStdLog(zap.L()) +} + +func replaceGlobalLogger(newOne *zap.Logger) { + zap.ReplaceGlobals(newOne) + zap.RedirectStdLog(newOne) +} + +func Init( + verbose bool, + formatter zapcore.Encoder, + mainLogName, logFilename, logDir string, + maxSizeMb, maxBackup, maxDay int, + logLevel zapcore.Level, + additionalOptions ...zap.Option, +) *zap.SugaredLogger { + level := zap.NewAtomicLevelAt(logLevel) + defaultWriter = rotater.NewLogWriter(logFilename, maxSizeMb, maxBackup, maxDay, logDir) + + defaultErrorOutputOptions = []zap.Option{zap.ErrorOutput(defaultWriter)} + options := defaultErrorOutputOptions + if verbose { + options = append(options, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.PanicLevel))) + } + // reset log option slice + options = append(options, additionalOptions...) + + log := initLogger(defaultWriter, mainLogName, formatter, level, options...) + + replaceGlobalLogger(log) + return log.Sugar() +} + +func New(parent *zap.SugaredLogger, moduleName string, options ...zap.Option) *zap.SugaredLogger { + var subLogger *zap.Logger + if parent == nil { + subLogger = zap.L().Named(moduleName) + } else { + subLogger = parent.Desugar().Named(moduleName) + } + + subLogger.WithOptions(options...) + + return subLogger.Sugar() +} + +func NewOtherLogger( + formatter zapcore.Encoder, + moduleName, logFilename, logDir string, + maxSizeMb, maxBackup, maxDay int, + logLevel zapcore.Level, + fields ...zapcore.Field, +) (logger *zap.SugaredLogger, closer func() error) { + loglevel := zap.NewAtomicLevelAt(logLevel) + logWriter := rotater.NewLogWriter(logFilename, maxSizeMb, maxBackup, maxDay, logDir) + core := zapcore.NewCore(formatter, logWriter, loglevel) + closer = logWriter.Close + logger = zap.New(core, defaultErrorOutputOptions...). + Named(moduleName).With(fields...).Sugar() + return +} + +func NewOtherLoggerWithOption( + formatter zapcore.Encoder, + moduleName, logFilename, logDir string, + maxSizeMb, maxBackup, maxDay int, + logLevel zapcore.Level, + options []zap.Option, + fields ...zapcore.Field, +) (logger *zap.SugaredLogger, closer func() error) { + loglevel := zap.NewAtomicLevelAt(logLevel) + logWriter := rotater.NewLogWriter(logFilename, maxSizeMb, maxBackup, maxDay, logDir) + core := zapcore.NewCore(formatter, logWriter, loglevel) + closer = logWriter.Close + options = append(defaultErrorOutputOptions, options...) + logger = zap.New(core, options...). + Named(moduleName).With(fields...).Sugar() + return +} +func initLogger( + writer zapcore.WriteSyncer, + moduleName string, + formatter zapcore.Encoder, + level zap.AtomicLevel, + options ...zap.Option, +) *zap.Logger { + core := zapcore.NewCore(formatter, writer, level) + return zap.New(core, options...).Named(moduleName) +} diff --git a/logger/zap/wrapped.go b/logger/zap/wrapped.go new file mode 100644 index 0000000..ec8b5c1 --- /dev/null +++ b/logger/zap/wrapped.go @@ -0,0 +1,14 @@ +package zap + +import ( + "go.uber.org/zap/zapcore" +) + +type zapWrappedSyncer struct { + zapcore.WriteSyncer +} + +func (r *zapWrappedSyncer) SetOnClose(closeFunc func()) {} +func (r *zapWrappedSyncer) Rotate() (err error) { return } +func (r *zapWrappedSyncer) Close() (err error) { return } +func (r *zapWrappedSyncer) Sync() error { return r.WriteSyncer.Sync() } diff --git a/misc/converse.go b/misc/converse.go new file mode 100644 index 0000000..6b218bf --- /dev/null +++ b/misc/converse.go @@ -0,0 +1,126 @@ +package misc + +import ( + "strconv" +) + + +func mustParseInt(value string, bits int) int64 { + if parsed, err := strconv.ParseInt(value, 10, bits); err != nil { + panic(err) + } else { + return parsed + } +} +func mustParseUint(value string, bits int) uint64 { + if parsed, err := strconv.ParseUint(value, 10, bits); err != nil { + panic(err) + } else { + return parsed + } +} + +func ParseUint8(value string) (ret *uint8) { + + if parsed, err := strconv.ParseUint(value, 10, 8); err == nil { + ret = new(uint8) + *ret = uint8(parsed) + } + return +} +func ParseUint16(value string) (ret *uint16) { + + if parsed, err := strconv.ParseUint(value, 10, 16); err == nil { + ret = new(uint16) + *ret = uint16(parsed) + } + return +} +func ParseUint32(value string) (ret *uint32) { + + if parsed, err := strconv.ParseUint(value, 10, 32); err == nil { + ret = new(uint32) + *ret = uint32(parsed) + } + return +} +func ParseUint64(value string) (ret *uint64) { + + if parsed, err := strconv.ParseUint(value, 10, 64); err == nil { + ret = new(uint64) + *ret = uint64(parsed) + } + return +} +func ParseUint(value string) (ret *uint) { + + if parsed, err := strconv.ParseUint(value, 10, 0); err == nil { + ret = new(uint) + *ret = uint(parsed) + } + return +} + +func ParseInt8(value string) (ret *int8) { + + if parsed, err := strconv.ParseInt(value, 10, 8); err == nil { + ret = new(int8) + *ret = int8(parsed) + } + return +} +func ParseInt16(value string) (ret *int16) { + + if parsed, err := strconv.ParseInt(value, 10, 16); err == nil { + ret = new(int16) + *ret = int16(parsed) + } + return +} +func ParseInt32(value string) (ret *int32) { + + if parsed, err := strconv.ParseInt(value, 10, 32); err == nil { + ret = new(int32) + *ret = int32(parsed) + } + return +} +func ParseInt64(value string) (ret *int64) { + + if parsed, err := strconv.ParseInt(value, 10, 64); err == nil { + ret = new(int64) + *ret = int64(parsed) + } + return +} +func ParseInt(value string) (ret *int) { + if parsed, err := strconv.ParseInt(value, 10, 0); err == nil { + ret = new(int) + *ret = int(parsed) + } + return +} + +func ParseUint8Must(value string) uint8 { return uint8(mustParseUint(value, 8)) } +func ParseUint16Must(value string) uint16 { return uint16(mustParseUint(value, 16)) } +func ParseUint32Must(value string) uint32 { return uint32(mustParseUint(value, 32)) } +func ParseUint64Must(value string) uint64 { return mustParseUint(value, 64) } +func ParseUintMust(value string) uint { return uint(mustParseUint(value, 0)) } + +func ParseInt8Must(value string) int8 { return int8(mustParseInt(value, 8)) } +func ParseInt16Must(value string) int16 { return int16(mustParseInt(value, 16)) } +func ParseInt32Must(value string) int32 { return int32(mustParseInt(value, 32)) } +func ParseInt64Must(value string) int64 { return mustParseInt(value, 64) } +func ParseIntMust(value string) int { return int(mustParseInt(value, 0)) } + +func FormatUint8(value uint8) string { return strconv.FormatUint(uint64(value), 10) } +func FormatUint16(value uint16) string { return strconv.FormatUint(uint64(value), 10) } +func FormatUint32(value uint32) string { return strconv.FormatUint(uint64(value), 10) } +func FormatUint64(value uint64) string { return strconv.FormatUint(value, 10) } +func FormatUint(value uint) string { return strconv.FormatUint(uint64(value), 10) } + +func FormatInt8(value int8) string { return strconv.FormatInt(int64(value), 10) } +func FormatInt16(value int16) string { return strconv.FormatInt(int64(value), 10) } +func FormatInt32(value int32) string { return strconv.FormatInt(int64(value), 10) } +func FormatInt64(value int64) string { return strconv.FormatInt(value, 10) } +func FormatInt(value int) string { return strconv.FormatInt(int64(value), 10) } diff --git a/misc/dumper.go b/misc/dumper.go new file mode 100644 index 0000000..8c68e04 --- /dev/null +++ b/misc/dumper.go @@ -0,0 +1,112 @@ +package misc + +import "bytes" + +import ( + "fmt" + "unicode/utf8" + "golang.org/x/text/width" +) + +func HexDump(by []byte) string { + n := len(by) + rowcount := 0 + stop := (n / 16) * 16 + k := 0 + buf := &bytes.Buffer{} + + for i := 0; i <= stop; i += 16 { + k++ + if i+16 < n { + rowcount = 16 + } else { + rowcount = min(k*16, n) % 16 + } + + fmt.Fprintf(buf, "%08x ", i) + for j := 0; j < rowcount; j++ { + if j%8 == 0 { + fmt.Fprintf(buf, " %02x ", by[i+j]) + } else { + fmt.Fprintf(buf, "%02x ", by[i+j]) + } + + } + + for j := rowcount; j < 16; j++ { + if j%8 == 0 { + fmt.Fprintf(buf, " ") + } else { + fmt.Fprintf(buf, " ") + } + } + buf.WriteRune('|') + viewString(by[i:(i + rowcount)], buf) + buf.WriteRune('|') + buf.WriteRune('\n') + buf.WriteRune('\r') + } + return buf.String() +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} +func min(a, b int) int { + if a < b { + return a + } + return b +} +func GuessUnicodeWidth(char rune) (realSize int) { + prop := width.LookupRune(char) + switch prop.Kind() { + case width.EastAsianFullwidth: + fallthrough + case width.EastAsianWide: + realSize = 2 + case width.EastAsianHalfwidth: + fallthrough + case width.EastAsianNarrow: + realSize = 2 + case width.EastAsianAmbiguous: + fallthrough + case width.Neutral: + fallthrough + default: + realSize = 1 + } + return +} + +func FillUnicodeWidth(byteLength int, char rune) int { + fillWidth := GuessUnicodeWidth(char) + return max(0, byteLength-fillWidth) +} +func viewString(b []byte, buf *bytes.Buffer) { + for { + if r, size := utf8.DecodeRune(b); size == 0 { + return + } else if r == utf8.RuneError { + for i := 0; i < size; i++ { + buf.WriteRune('_') + } + b = b[size:] + } else if r < 32 { + for i := 0; i < size; i++ { + buf.WriteRune('.') + } + b = b[size:] + } else { + buf.WriteRune(r) + pad := FillUnicodeWidth(size, r) + for i := 0; i < pad; i++ { + buf.WriteRune('.') + } + b = b[size:] + } + } +} diff --git a/misc/uuid.go b/misc/uuid.go new file mode 100644 index 0000000..0ccbd73 --- /dev/null +++ b/misc/uuid.go @@ -0,0 +1,118 @@ +package misc + +import ( + "bytes" + "database/sql/driver" + "encoding/hex" + "errors" + "github.com/NebulousLabs/fastrand" + "fmt" +) + +type UUID [16]byte + +func (u UUID) Marshal() ([]byte, error) { + return u[:], nil +} + +func (u UUID) MarshalTo(buf []byte) (n int, err error) { + if len(u) == 0 { + return 0, nil + } + copy(buf, u[:]) + return len(u), nil +} +func (u *UUID) Unmarshal(buf []byte) error { + if len(buf) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(buf)) + } + copy(u[:], buf) + return nil +} + +func (u UUID) Compare(other UUID) int { + return bytes.Compare(u[:], other[:]) +} + +func (u UUID) Equal(other UUID) bool { + return u.Compare(other) == 0 +} +func (u *UUID) UnmarshalJSON(from []byte) error { + quote := []byte("\"") + quoteSize := len(quote) + + if len(from) < quoteSize*2 { + return errors.New("invalid quote notation") + } + + if !bytes.HasPrefix(from, quote) || !bytes.HasSuffix(from, quote) { + return errors.New("invalid quote notation") + } else if err := u.Unmarshal(from[quoteSize:len(from)-quoteSize]); err != nil { + return err + } + return nil +} + +func (u UUID) MarshalJSON() ([]byte, error) { + var buffer bytes.Buffer + buffer.WriteRune('"') + buffer.WriteString(hex.EncodeToString(u[:])) + buffer.WriteRune('"') + return buffer.Bytes(), nil +} + +func (u *UUID) Size() int { + if u == nil { + return 0 + } + if len(*u) == 0 { + return 0 + } + return 16 +} + +func NewUUID() (u UUID) { + newObj := UUID{} + newObj.Random() + return newObj +} + +func (u *UUID) UUIDFromHexString(buf []byte) (error) { + hexBuf := make([]byte, hex.DecodedLen(len(buf))) + if _, err := hex.Decode(hexBuf, buf); err != nil { + return err + } else if err := u.Unmarshal(hexBuf); err != nil { + return err + } + return nil +} + +func (u UUID) ToHexString() (string) { + return hex.EncodeToString(u[:]) +} + +// Scan implements the Scanner interface. +func (u *UUID) Scan(src interface{}) error { + if src == nil { + return nil + } + + b, ok := src.([]byte) + if !ok { + return errors.New("Scan source was not []bytes") + } + + u.UUIDFromHexString(b) + return nil +} + +// Value implements the driver Valuer interface. +func (u UUID) Value() (driver.Value, error) { + return u.ToHexString(), nil +} + +func (u UUID) Random() { + fastrand.Read(u[:]) + u[6] = (u[6] & 0x0f) | 0x40 // Version 4 + u[8] = (u[8] & 0x3f) | 0x80 // Variant is 10 +} diff --git a/net/ipv4_resolver.go b/net/ipv4_resolver.go new file mode 100644 index 0000000..a7c497d --- /dev/null +++ b/net/ipv4_resolver.go @@ -0,0 +1,41 @@ +package net + +import ( + "context" + "github.com/benburkert/dns" + "net" +) + +func ReplaceDefaultDnsResolver(){ + cli := &dns.Client{Resolver: &PreferIPV4Resolver{}} + net.DefaultResolver.PreferGo = true + net.DefaultResolver.Dial = cli.Dial +} + +type PreferIPV4Resolver struct{} + +func (p *PreferIPV4Resolver) ServeDNS(ctx context.Context, w dns.MessageWriter, r *dns.Query) { + if r.Questions[0].Type == dns.TypeAAAA { + w.Status(dns.ServFail) + } else if msg, err := w.Recur(ctx); err != nil { + w.Status(dns.ServFail) + } else { + p.writeMessage(w, msg) + } +} + +func (p *PreferIPV4Resolver) writeMessage(w dns.MessageWriter, msg *dns.Message) { + w.Status(msg.RCode) + w.Authoritative(msg.Authoritative) + w.Recursion(msg.RecursionAvailable) + + for _, res := range msg.Answers { + w.Answer(res.Name, res.TTL, res.Record) + } + for _, res := range msg.Authorities { + w.Authority(res.Name, res.TTL, res.Record) + } + for _, res := range msg.Additionals { + w.Additional(res.Name, res.TTL, res.Record) + } +} diff --git a/net/ipv4_resolver_test.go b/net/ipv4_resolver_test.go new file mode 100644 index 0000000..b0cd35d --- /dev/null +++ b/net/ipv4_resolver_test.go @@ -0,0 +1,37 @@ +package net + +import ( + "testing" + "net" + "github.com/benburkert/dns" +) + +func TestPreferIPV4Resolver(t *testing.T) { + var ( + cli = &dns.Client{Resolver: &PreferIPV4Resolver{}} + addrs []net.IP + err error + ) + addrs, err = net.LookupIP("www.v6.facebook.com") + if err == nil && len(addrs) != 0 && len(addrs[0]) == net.IPv6len { + err = nil + t.Log("checked AAAA dns reponse") + } else { + t.Fatal("unknown error : ", err) + } + + net.DefaultResolver.PreferGo = true + net.DefaultResolver.Dial = cli.Dial + + addrs, err = net.LookupIP("www.v6.facebook.com") + if len(addrs) != 0 { + t.Fatal("ipv6 address isn't empty") + } else if err == nil { + t.Fatal("no error in AAAA record") + } else if dnserr, ok := err.(*net.DNSError); !ok || dnserr.Err != "no such host" { + t.Fatal("unknown error : ", err) + } else { + err = nil + t.Log("AAAA dns request filtered") + } +} diff --git a/net/local.go b/net/local.go new file mode 100644 index 0000000..3d32e1c --- /dev/null +++ b/net/local.go @@ -0,0 +1,36 @@ +package net + +import "net" + +// GetLocalIP returns the non loopback local IP of the host +func GetLocalIP() string { + addrs, err := net.InterfaceAddrs() + if err != nil { + return "" + } + for _, address := range addrs { + // check the address type and if it is not a loopback the display it + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String() + } + } + } + return "" +} + +func ResolveIp(name string) (net.IP, error) { + if addrs, err := net.ResolveIPAddr("ip4", name); err != nil { + return nil, err + } else { + return addrs.IP, nil + } +} + +func ExtractIp(remoteAddr net.Addr) string { + if addr, ok := remoteAddr.(*net.TCPAddr); ok { + return addr.IP.String() + } else { + return remoteAddr.String() + } +} diff --git a/tty/code/termcodes.go b/tty/code/termcodes.go new file mode 100644 index 0000000..b2c193f --- /dev/null +++ b/tty/code/termcodes.go @@ -0,0 +1,212 @@ +// +build !windows + +package code + +import ( + "os" + "syscall" +) + +// POSIX terminal mode flags as listed in RFC 4254 Section 8. +const ( + tty_OP_END = 0 + VINTR = 1 + VQUIT = 2 + VERASE = 3 + VKILL = 4 + VEOF = 5 + VEOL = 6 + VEOL2 = 7 + VSTART = 8 + VSTOP = 9 + VSUSP = 10 + VDSUSP = 11 + VREPRINT = 12 + VWERASE = 13 + VLNEXT = 14 + VFLUSH = 15 + VSWTCH = 16 + VSTATUS = 17 + VDISCARD = 18 + IGNPAR = 30 + PARMRK = 31 + INPCK = 32 + ISTRIP = 33 + INLCR = 34 + IGNCR = 35 + ICRNL = 36 + IUCLC = 37 + IXON = 38 + IXANY = 39 + IXOFF = 40 + IMAXBEL = 41 + IUTF8 = 42 + ISIG = 50 + ICANON = 51 + XCASE = 52 + ECHO = 53 + ECHOE = 54 + ECHOK = 55 + ECHONL = 56 + NOFLSH = 57 + TOSTOP = 58 + IEXTEN = 59 + ECHOCTL = 60 + ECHOKE = 61 + PENDIN = 62 + OPOST = 70 + OLCUC = 71 + ONLCR = 72 + OCRNL = 73 + ONOCR = 74 + ONLRET = 75 + CS7 = 90 + CS8 = 91 + PARENB = 92 + PARODD = 93 + TTY_OP_ISPEED = 128 + TTY_OP_OSPEED = 129 +) + +// struct termios { +// tcflag_t c_iflag; /* input modes */ +// tcflag_t c_oflag; /* output modes */ +// tcflag_t c_cflag; /* control modes */ +// tcflag_t c_lflag; /* local modes */ +// cc_t c_cc[NCCS]; /* special characters */ +// speed_t c_ispeed; +// speed_t c_ospeed; +// }; + +type Setter interface { + Set(pty *os.File, termios *syscall.Termios, value uint32) error +} + +var TermAttrSetters = map[uint8]Setter{ + VINTR: &ccSetter{Character: syscall.VINTR}, + VQUIT: &ccSetter{Character: syscall.VQUIT}, + VERASE: &ccSetter{Character: syscall.VERASE}, + VKILL: &ccSetter{Character: syscall.VKILL}, + VEOF: &ccSetter{Character: syscall.VEOF}, + VEOL: &ccSetter{Character: syscall.VEOL}, + VEOL2: &ccSetter{Character: syscall.VEOL2}, + VSTART: &ccSetter{Character: syscall.VSTART}, + VSTOP: &ccSetter{Character: syscall.VSTOP}, + VSUSP: &ccSetter{Character: syscall.VSUSP}, + VDSUSP: &nopSetter{}, + VREPRINT: &ccSetter{Character: syscall.VREPRINT}, + VWERASE: &ccSetter{Character: syscall.VWERASE}, + VLNEXT: &ccSetter{Character: syscall.VLNEXT}, + VFLUSH: &nopSetter{}, + VSWTCH: &nopSetter{}, + VSTATUS: &nopSetter{}, + VDISCARD: &ccSetter{Character: syscall.VDISCARD}, + + // Input modes + IGNPAR: &iflagSetter{Flag: syscall.IGNPAR}, + PARMRK: &iflagSetter{Flag: syscall.PARMRK}, + INPCK: &iflagSetter{Flag: syscall.INPCK}, + ISTRIP: &iflagSetter{Flag: syscall.ISTRIP}, + INLCR: &iflagSetter{Flag: syscall.INLCR}, + IGNCR: &iflagSetter{Flag: syscall.IGNCR}, + ICRNL: &iflagSetter{Flag: syscall.ICRNL}, + IUCLC: &nopSetter{}, + IXON: &iflagSetter{Flag: syscall.IXON}, + IXANY: &iflagSetter{Flag: syscall.IXANY}, + IXOFF: &iflagSetter{Flag: syscall.IXOFF}, + IMAXBEL: &iflagSetter{Flag: syscall.IMAXBEL}, + IUTF8: &iflagSetter{Flag: syscall.IUTF8}, //IUTF8 + + // Local modes + ISIG: &lflagSetter{Flag: syscall.ISIG}, + ICANON: &lflagSetter{Flag: syscall.ICANON}, + XCASE: &nopSetter{}, + ECHO: &lflagSetter{Flag: syscall.ECHO}, + ECHOE: &lflagSetter{Flag: syscall.ECHOE}, + ECHOK: &lflagSetter{Flag: syscall.ECHOK}, + ECHONL: &lflagSetter{Flag: syscall.ECHONL}, + NOFLSH: &lflagSetter{Flag: syscall.NOFLSH}, + TOSTOP: &lflagSetter{Flag: syscall.TOSTOP}, + IEXTEN: &lflagSetter{Flag: syscall.IEXTEN}, + ECHOCTL: &lflagSetter{Flag: syscall.ECHOCTL}, + ECHOKE: &lflagSetter{Flag: syscall.ECHOKE}, + PENDIN: &lflagSetter{Flag: syscall.PENDIN}, + + // Output modes + OPOST: &oflagSetter{Flag: syscall.OPOST}, + OLCUC: &nopSetter{}, + ONLCR: &oflagSetter{Flag: syscall.ONLCR}, + OCRNL: &oflagSetter{Flag: syscall.OCRNL}, + ONOCR: &oflagSetter{Flag: syscall.ONOCR}, + ONLRET: &oflagSetter{Flag: syscall.ONLRET}, + + // Control modes + CS7: &cflagSetter{Flag: syscall.CS7}, + CS8: &cflagSetter{Flag: syscall.CS8}, + PARENB: &cflagSetter{Flag: syscall.PARENB}, + PARODD: &cflagSetter{Flag: syscall.PARODD}, + + // Baud rates (ignore) + TTY_OP_ISPEED: &nopSetter{}, + TTY_OP_OSPEED: &nopSetter{}, +} + +type nopSetter struct{} + +type ccSetter struct { + Character uint8 +} + +func (cc *ccSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error { + termios.Cc[cc.Character] = byte(value) + return SetAttr(pty, termios) +} + +func (i *iflagSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error { + if value == 0 { + termios.Iflag &^= i.Flag + } else { + termios.Iflag |= i.Flag + } + return SetAttr(pty, termios) +} + +func (l *lflagSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error { + if value == 0 { + termios.Lflag &^= l.Flag + } else { + termios.Lflag |= l.Flag + } + return SetAttr(pty, termios) +} + +func (o *oflagSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error { + if value == 0 { + termios.Oflag &^= o.Flag + } else { + termios.Oflag |= o.Flag + } + + return SetAttr(pty, termios) +} + +func (c *cflagSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error { + switch c.Flag { + // CSIZE is a field + case syscall.CS7, syscall.CS8: + termios.Cflag &^= syscall.CSIZE + termios.Cflag |= c.Flag + default: + if value == 0 { + termios.Cflag &^= c.Flag + } else { + termios.Cflag |= c.Flag + } + } + + return SetAttr(pty, termios) +} + +func (n *nopSetter) Set(pty *os.File, termios *syscall.Termios, value uint32) error { + return nil +} diff --git a/tty/code/termcodes_darwin.go b/tty/code/termcodes_darwin.go new file mode 100644 index 0000000..098f31a --- /dev/null +++ b/tty/code/termcodes_darwin.go @@ -0,0 +1,45 @@ +// +build darwin + +package code + +import ( + "os" + "syscall" + "unsafe" +) + +type iflagSetter struct { + Flag uint64 +} + +type lflagSetter struct { + Flag uint64 +} + +type oflagSetter struct { + Flag uint64 +} + +type cflagSetter struct { + Flag uint64 +} + +func SetAttr(tty *os.File, termios *syscall.Termios) error { + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios))) + if r != 0 { + return os.NewSyscallError("SYS_IOCTL", e) + } + + return nil +} + +func GetAttr(tty *os.File) (*syscall.Termios, error) { + termios := &syscall.Termios{} + + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios))) + if r != 0 { + return nil, os.NewSyscallError("SYS_IOCTL", e) + } + + return termios, nil +} diff --git a/tty/code/termcodes_linux.go b/tty/code/termcodes_linux.go new file mode 100644 index 0000000..1134ae9 --- /dev/null +++ b/tty/code/termcodes_linux.go @@ -0,0 +1,45 @@ +// +build linux + +package code + +import ( + "os" + "syscall" + "unsafe" +) + +type iflagSetter struct { + Flag uint32 +} + +type lflagSetter struct { + Flag uint32 +} + +type oflagSetter struct { + Flag uint32 +} + +type cflagSetter struct { + Flag uint32 +} + +func SetAttr(tty *os.File, termios *syscall.Termios) error { + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TCSETS, uintptr(unsafe.Pointer(termios))) + if r != 0 { + return os.NewSyscallError("SYS_IOCTL", e) + } + + return nil +} + +func GetAttr(tty *os.File) (*syscall.Termios, error) { + termios := &syscall.Termios{} + + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TCGETS, uintptr(unsafe.Pointer(termios))) + if r != 0 { + return nil, os.NewSyscallError("SYS_IOCTL", e) + } + + return termios, nil +} diff --git a/tty/term.go b/tty/term.go new file mode 100644 index 0000000..7e48458 --- /dev/null +++ b/tty/term.go @@ -0,0 +1,114 @@ +package tty + +import ( + "bytes" +) + +// Window represents the size of a PTY window. +type Window struct { + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 +} + +// Pty represents a PTY request and configuration. +type Pty struct { + Term string + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 + Modelist string +} + +type Environ struct { + Key string + Value string +} + +type TermInfo struct { + Term string + Cols uint32 + Rows uint32 + Modelist string +} + +type CommandLine struct { + Command string +} +type ExitStatus struct { + Status uint32 +} + +func (i *TermInfo) ToWindow() (*Window) { + if i == nil { + return nil + } + + return &Window{ + Columns: i.Cols, + Rows: i.Rows, + Width: 8 * i.Cols, + Height: 8 * i.Rows, + } +} + +func (i *TermInfo) ToPty() (*Pty) { + if i == nil { + return nil + } + + return &Pty{ + Term: i.Term, + Columns: i.Cols, + Rows: i.Rows, + Width: 8 * i.Cols, + Height: 8 * i.Rows, + Modelist: i.Modelist, + } +} + +func (i *TermInfo) TermMap() (mode map[uint8]uint32) { + mode = make(map[uint8]uint32) + if i == nil { + return + } + reader := bytes.NewReader([]byte(i.Modelist)) + buf := make([]byte, 4) + for { + if k, err := reader.ReadByte(); err != nil { + break + } else if read, err := reader.Read(buf); err != nil { + break + } else if read != 4 { + break + } else { + mode[uint8(k)] = uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<9 | uint32(buf[3])<<0 + } + } + return mode +} +func (p *Pty) ToTermInfo() (*TermInfo) { + if p == nil { + return nil + } + return &TermInfo{ + Term: p.Term, + Cols: p.Columns, + Rows: p.Rows, + Modelist: p.Modelist, + } +} + +func (p *Pty) ToWindow() (*Window) { + if p == nil { + return nil + } + return &Window{ + Columns: p.Columns, + Rows: p.Rows, + Width: 8 * p.Columns, + Height: 8 * p.Rows, + } +}