유틸클레스 잔뜩 추가
This commit is contained in:
parent
e0bebbbb06
commit
251ba74dbe
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package logger
|
||||
|
||||
import "io"
|
||||
|
||||
type WriteSyncer interface {
|
||||
io.Writer
|
||||
Sync() error
|
||||
}
|
||||
|
||||
type RotateSyncer interface {
|
||||
WriteSyncer
|
||||
SetOnClose(func())
|
||||
Rotate() error
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 }
|
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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() }
|
|
@ -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) }
|
|
@ -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:]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
Reference in New Issue