├── example ├── manifest.json └── echo.go ├── version └── version.go ├── cocaine12 ├── proxy │ ├── README.md │ ├── proxy_test.go │ └── proxy.go ├── version.go ├── doc.go ├── bridge │ ├── sysprocattr_darwin.go │ ├── sysprocattr_linux.go │ ├── ping.go │ ├── args_test.go │ └── bridge.go ├── trace_test.go ├── serviceinfo.go ├── stack.go ├── cocainetest │ ├── request.go │ ├── example_test.go │ └── response.go ├── severity.go ├── logger_test.go ├── asocket_test.go ├── rx_test.go ├── sessions.go ├── logger.go ├── entry.go ├── worker_protocol.go ├── dispatch.go ├── workerv1_bench_test.go ├── utils.go ├── defaults_test.go ├── locator.go ├── worker_handlers.go ├── http_test.go ├── apilocator.go ├── utils_test.go ├── token.go ├── worker.go ├── fallbacklogger.go ├── httpresponse.go ├── defaults.go ├── wprotov1.go ├── clogger.go ├── channel.go ├── service_test.go ├── trace.go ├── protocol.go ├── handler.go ├── asocket.go ├── httphandler.go └── protocol_test.go ├── .travis.yml ├── AUTHORS ├── cmd ├── proxy │ └── main.go └── bridge_main.go ├── .gitignore ├── vendor └── github.com │ └── ugorji │ └── go │ ├── README.md │ ├── msgpack.org.md │ ├── LICENSE │ └── codec │ ├── ext_dep_test.go │ ├── z_helper_test.go │ ├── helper_internal.go │ ├── msgpack_test.py │ ├── rpc.go │ ├── 0doc.go │ ├── time.go │ └── README.md ├── Makefile ├── cocaine ├── message_test.go ├── sessionkeeper.go ├── http_test.go ├── doc.go ├── locator.go ├── response_writer.go ├── logger.go ├── httpreq.go ├── message.go ├── asocket.go └── worker.go ├── README.md ├── auth └── tvm │ └── tvm.go ├── Bridge.md └── LICENSE /example/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "slave": "echo" 3 | } 4 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var Version = "" 4 | -------------------------------------------------------------------------------- /cocaine12/proxy/README.md: -------------------------------------------------------------------------------- 1 | # proxy 2 | 3 | it's for load-testing of framework only 4 | -------------------------------------------------------------------------------- /cocaine12/version.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | const ( 4 | frameworkVersion = "0.12.5.1" 5 | ) 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7 5 | 6 | install: 7 | - go get -u github.com/golang/lint/golint 8 | -------------------------------------------------------------------------------- /cocaine12/doc.go: -------------------------------------------------------------------------------- 1 | // Package cocaine12 provides primitives, interfaces and structs 2 | // to work with Cocaine Application Engine 3 | package cocaine12 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Alexander Ponomarev 2 | Anton Tiurin 3 | Maxim Bublis 4 | Vladimir Petrov 5 | Vyacheslav Bakhmutov 6 | -------------------------------------------------------------------------------- /cocaine12/bridge/sysprocattr_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | 3 | package bridge 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | func getSysProctAttr() *syscall.SysProcAttr { 10 | return &syscall.SysProcAttr{} 11 | } 12 | -------------------------------------------------------------------------------- /cocaine12/trace_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkTraceWith(b *testing.B) { 8 | ctx := BeginNewTraceContext(nil) 9 | for n := 0; n < b.N; n++ { 10 | _, _ = NewSpan(ctx, "bench") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /cocaine12/bridge/sysprocattr_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package bridge 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | func getSysProctAttr() *syscall.SysProcAttr { 10 | attrs := &syscall.SysProcAttr{ 11 | Pdeathsig: syscall.SIGKILL, 12 | } 13 | 14 | return attrs 15 | } 16 | -------------------------------------------------------------------------------- /cmd/proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/cocaine/cocaine-framework-go/cocaine12/proxy" 8 | ) 9 | 10 | func main() { 11 | err := http.ListenAndServe(":10000", proxy.NewServer()) 12 | if err != nil { 13 | log.Fatal("ListenAndServe: ", err) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | coverage.out 24 | -------------------------------------------------------------------------------- /cocaine12/bridge/ping.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | func ping(address string, timeout time.Duration) error { 9 | d := net.Dialer{ 10 | Timeout: timeout, 11 | DualStack: true, 12 | } 13 | 14 | conn, err := d.Dial("tcp", address) 15 | if err != nil { 16 | return err 17 | } 18 | defer conn.Close() 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /cocaine12/serviceinfo.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // EndpointItem is one of possible endpoints of a service 9 | type EndpointItem struct { 10 | // Service ip address 11 | IP string 12 | // Service port 13 | Port uint64 14 | } 15 | 16 | func (e *EndpointItem) String() string { 17 | return net.JoinHostPort(e.IP, fmt.Sprintf("%d", e.Port)) 18 | } 19 | -------------------------------------------------------------------------------- /cocaine12/stack.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | // dump all stacks 8 | func dumpStack() []byte { 9 | var ( 10 | buf []byte 11 | stackSize int 12 | 13 | bufferLen = 16384 14 | ) 15 | 16 | for stackSize == len(buf) { 17 | buf = make([]byte, bufferLen) 18 | stackSize = runtime.Stack(buf, true) 19 | bufferLen *= 2 20 | } 21 | 22 | return buf[:stackSize] 23 | } 24 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/README.md: -------------------------------------------------------------------------------- 1 | # go 2 | 3 | Collection of Open-Source Go libraries and tools. 4 | 5 | ## Codec 6 | 7 | [Codec](https://github.com/ugorji/go/tree/master/codec#readme) is a High Performance and Feature-Rich Idiomatic encode/decode and rpc library for [msgpack](http://msgpack.org) and [Binc](https://github.com/ugorji/binc). 8 | 9 | Online documentation is at [http://godoc.org/github.com/ugorji/go/codec]. 10 | 11 | Install using: 12 | 13 | go get github.com/ugorji/go/codec 14 | 15 | -------------------------------------------------------------------------------- /cocaine12/cocainetest/request.go: -------------------------------------------------------------------------------- 1 | package cocainetest 2 | 3 | import ( 4 | "context" 5 | 6 | cocaine "github.com/cocaine/cocaine-framework-go/cocaine12" 7 | ) 8 | 9 | type Request struct { 10 | chunks [][]byte 11 | } 12 | 13 | var _ cocaine.Request = NewRequest() 14 | 15 | func NewRequest() *Request { 16 | return &Request{ 17 | chunks: make([][]byte, 0, 10), 18 | } 19 | } 20 | 21 | func (r *Request) Write(p []byte) (int, error) { 22 | r.chunks = append(r.chunks, p) 23 | return len(p), nil 24 | } 25 | 26 | func (r *Request) Read(ctx context.Context) (chunk []byte, err error) { 27 | if len(r.chunks) == 0 { 28 | return nil, cocaine.ErrStreamIsClosed 29 | } 30 | 31 | chunk, r.chunks = r.chunks[0], r.chunks[1:] 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /cocaine12/severity.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "strconv" 5 | "sync/atomic" 6 | ) 7 | 8 | type Severity int32 9 | 10 | const ( 11 | DebugLevel Severity = iota 12 | InfoLevel 13 | WarnLevel 14 | ErrorLevel = 3 15 | ) 16 | 17 | func (s *Severity) String() string { 18 | switch i := s.get(); i { 19 | case DebugLevel: 20 | return "DEBUG" 21 | case InfoLevel: 22 | return "INFO" 23 | case WarnLevel: 24 | return "WARNING" 25 | case ErrorLevel: 26 | return "ERROR" 27 | default: 28 | return strconv.FormatInt(int64(i), 10) 29 | } 30 | } 31 | 32 | func (s *Severity) get() Severity { 33 | return Severity(atomic.LoadInt32((*int32)(s))) 34 | } 35 | 36 | func (s *Severity) set(value Severity) { 37 | atomic.StoreInt32((*int32)(s), int32(value)) 38 | } 39 | -------------------------------------------------------------------------------- /cocaine12/logger_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestLogger(t *testing.T) { 9 | ctx := context.Background() 10 | log, err := NewLogger(ctx) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | defer log.Close() 15 | log.WithFields(Fields{"a": 1, "b": 2}).Errf("Error %v", log.Verbosity(ctx)) 16 | log.WithFields(Fields{"a": 1, "b": 2}).Warnf("Warning %v", log.Verbosity(ctx)) 17 | log.WithFields(Fields{"a": 1, "b": 2}).Infof("Info %v", log.Verbosity(ctx)) 18 | log.WithFields(Fields{"a": 1, "b": 2}).Debugf("Debug %v", log.Verbosity(ctx)) 19 | } 20 | 21 | func BenchmarkFormatFields5(b *testing.B) { 22 | fields := Fields{ 23 | "A": 1, 24 | "B": 2, 25 | "C": 3, 26 | "TEXT": "TEXT", 27 | } 28 | for i := 0; i < b.N; i++ { 29 | formatFields(fields) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cocaine12/proxy/proxy_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestMain(t *testing.T) { 12 | mux := NewServer() 13 | ts := httptest.NewServer(mux) 14 | defer ts.Close() 15 | 16 | res, err := http.Get(ts.URL) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | assert.Equal(t, http.StatusBadRequest, res.StatusCode) 21 | 22 | res, err = http.Get(ts.URL + "/A") 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | assert.Equal(t, http.StatusBadRequest, res.StatusCode) 27 | 28 | res, err = http.Get(ts.URL + "/A/B") 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | res, err = http.Get(ts.URL + "/A/B/URL") 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | assert.Equal(t, http.StatusOK, res.StatusCode) 38 | } 39 | -------------------------------------------------------------------------------- /cocaine12/bridge/args_test.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFilterArgs(t *testing.T) { 10 | var input = []string{ 11 | "--locator", "host1:10053,127.0.0.1:10054", 12 | "--uuid", "uuid", 13 | "--endpoint", "/var/run/cocaine/sock", 14 | "--protocol", "1"} 15 | 16 | result := filterEndpointArg(input) 17 | assert.Equal(t, []string{"--locator", "host1:10053,127.0.0.1:10054", "--uuid", "uuid", "--protocol", "1"}, result) 18 | 19 | var input2 = []string{ 20 | "--locator", "host1:10053,127.0.0.1:10054", 21 | "--uuid", "uuid", 22 | "--protocol", "1", 23 | "--endpoint", "/var/run/cocaine/sock"} 24 | result = filterEndpointArg(input2) 25 | assert.Equal(t, []string{"--locator", "host1:10053,127.0.0.1:10054", "--uuid", "uuid", "--protocol", "1"}, result) 26 | } 27 | -------------------------------------------------------------------------------- /cocaine12/cocainetest/example_test.go: -------------------------------------------------------------------------------- 1 | package cocainetest 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | cocaine "github.com/cocaine/cocaine-framework-go/cocaine12" 8 | ) 9 | 10 | func Example() { 11 | var handler cocaine.EventHandler // check type 12 | 13 | handler = func(ctx context.Context, req cocaine.Request, resp cocaine.Response) { 14 | inp, _ := req.Read(ctx) 15 | 16 | resp.Write(inp) 17 | resp.ErrorMsg(100, "testerrormessage") 18 | resp.Close() 19 | } 20 | 21 | mockRequest := NewRequest() 22 | mockRequest.Write([]byte("PING")) 23 | 24 | mockResponse := NewResponse() 25 | 26 | handler(context.Background(), mockRequest, mockResponse) 27 | 28 | fmt.Printf("data: %s error: %d %s \n", 29 | mockResponse.Bytes(), mockResponse.Err.Code, mockResponse.Err.Msg) 30 | 31 | // Output: data: PING error: 100 testerrormessage 32 | } 33 | -------------------------------------------------------------------------------- /cocaine12/asocket_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestASocketDrain(t *testing.T) { 11 | var exit = make(chan struct{}) 12 | buff := newAsyncBuf() 13 | 14 | var ( 15 | count = 0 16 | expected = 3 17 | ) 18 | 19 | msg := &Message{} 20 | for i := 0; i < expected; i++ { 21 | buff.in <- msg 22 | } 23 | 24 | go func() { 25 | buff.Drain(1 * time.Second) 26 | close(exit) 27 | }() 28 | 29 | for m := range buff.out { 30 | count++ 31 | assert.Equal(t, msg, m) 32 | } 33 | 34 | assert.Equal(t, expected, count) 35 | <-exit 36 | } 37 | 38 | func TestASocketConnect(t *testing.T) { 39 | _, err := newTCPConnection("128.0.0.1:45000", time.Second) 40 | assert.Error(t, err) 41 | _, err = newUnixConnection("unix.sock", time.Second) 42 | assert.Error(t, err) 43 | } 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all fmt vet lint test travis 2 | 3 | all: deps fmt test 4 | 5 | GIT_VERSION := $(shell git describe --abbrev=8 --dirty --always) 6 | GO_LDFLAGS=-ldflags "-X `go list ./version`.Version $(GIT_VERSION)" 7 | 8 | bridge: deps 9 | go build -o bridge $(GO_LDFLAGS) ./cmd/bridge_main.go 10 | 11 | 12 | deps: 13 | go get -t ./cocaine12/... 14 | 15 | 16 | fmt: 17 | @echo "+ $@" 18 | @test -z "$$(gofmt -s -l ./cocaine12/ | grep -v Godeps/_workspace/src/ | tee /dev/stderr)" || \ 19 | echo "+ please format Go code with 'gofmt -s'" 20 | 21 | lint: 22 | @echo "+ $@" 23 | @test -z "$$(golint ./cocaine12/... | grep -v Godeps/_workspace/src/ | grep -v cocaine12/old | tee /dev/stderr)" 24 | 25 | 26 | vet: 27 | @echo "+ $@" 28 | @go vet ./cocaine12/... 29 | 30 | 31 | test: 32 | @echo "+ $@" 33 | @go test -v -test.short -cover -race github.com/cocaine/cocaine-framework-go/cocaine12 34 | 35 | 36 | -------------------------------------------------------------------------------- /example/echo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cocaine/cocaine-framework-go/cocaine12" 6 | "golang.org/x/net/context" 7 | "time" 8 | ) 9 | 10 | func Echo(ctx context.Context, req cocaine12.Request, resp cocaine12.Response) { 11 | defer resp.Close() 12 | 13 | ctx, done := cocaine12.NewSpan(ctx, "echo") 14 | defer done() 15 | 16 | body, err := req.Read(ctx) 17 | if err != nil { 18 | resp.ErrorMsg(999, err.Error()) 19 | return 20 | } 21 | 22 | time.Sleep(time.Millisecond * 100) 23 | resp.Write(body) 24 | } 25 | 26 | func main() { 27 | w, err := cocaine12.NewWorker() 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | w.SetTerminationHandler(func(ctx context.Context) { 33 | time.Sleep(60 * time.Millisecond) 34 | }) 35 | 36 | w.EnableStackSignal(false) 37 | w.On("ping", Echo) 38 | 39 | if err = w.Run(nil); err != nil { 40 | fmt.Printf("%v", err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cocaine12/rx_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "log" 6 | ) 7 | 8 | func Example_ApplicationClient() { 9 | ctx := context.Background() 10 | s, err := NewService(ctx, "log", nil) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | 15 | // Create channle to communicate with method "ping" 16 | channel, err := s.Call(ctx, "enqueue", "ping") 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | // `enqueue` accepts stream, so send the chunk of data 22 | if err := channel.Call(ctx, "write", "AAAAAA"); err != nil { 23 | log.Fatal(err) 24 | } 25 | defer channel.Call(ctx, "close") 26 | 27 | // receive the answer from the application 28 | answer, err := channel.Get(ctx) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | var res struct { 34 | Res string 35 | } 36 | 37 | // Unpack to an anonymous struct 38 | if err := answer.Extract(&res); err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | log.Printf("%s", res.Res) 43 | } 44 | -------------------------------------------------------------------------------- /cocaine/message_test.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ugorji/go/codec" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMessageUnpackErrorMsg(t *testing.T) { 11 | const ( 12 | session = 1 13 | _type = 5 14 | errorCode = 1 15 | errorMessage = "someerror" 16 | ) 17 | // msgpack.packb([5, 1, [1, "someerror"]]) 18 | payload := []byte{147, 5, 1, 146, 1, 169, 115, 111, 109, 101, 101, 114, 114, 111, 114} 19 | var result []interface{} 20 | 21 | assert.NoError(t, codec.NewDecoderBytes(payload, h).Decode(&result)) 22 | 23 | msg, err := unpackMessage(result) 24 | assert.NoError(t, err) 25 | 26 | assert.Equal(t, int64(session), msg.getSessionID(), "bad session") 27 | if !assert.Equal(t, int64(_type), msg.getTypeID(), "bad message type") { 28 | t.FailNow() 29 | } 30 | 31 | e := msg.(*errorMsg) 32 | assert.Equal(t, errorCode, e.Code, "bad error code") 33 | assert.Equal(t, errorMessage, e.Message, "bad error message") 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cocaine-framework-go [![Build Status][travis-image]][travis-url] 2 | 3 | This package helps you to write a golang application, which can be launched in [Cocaine](https://github.com/cocaine/cocaine-core). 4 | 5 | You can write application for Cocaine so fast and easy as you cannot even imagine. 6 | 7 | # Documentation 8 | 9 | Version | Refs 10 | ---------|-------------- 11 | v0.12 | [![Godoc12][go-doc-image-12]][go-doc-url-12] 12 | v0.11 | [![Godoc11][go-doc-image-11]][go-doc-url-11] 13 | 14 | [go-doc-url-12]: http://godoc.org/github.com/cocaine/cocaine-framework-go/cocaine12 15 | [go-doc-image-12]: https://godoc.org/github.com/noxiouz/cocaine/cocaine-framework-go/cocaine12?status.png 16 | 17 | [go-doc-url-11]: http://godoc.org/github.com/cocaine/cocaine-framework-go/cocaine 18 | [go-doc-image-11]: https://godoc.org/github.com/noxiouz/cocaine/cocaine-framework-go/cocaine?status.png 19 | 20 | [travis-image]: https://travis-ci.org/cocaine/cocaine-framework-go.png?branch=master 21 | [travis-url]: http://travis-ci.org/cocaine/cocaine-framework-go 22 | -------------------------------------------------------------------------------- /cocaine12/cocainetest/response.go: -------------------------------------------------------------------------------- 1 | package cocainetest 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "syscall" 7 | 8 | "github.com/cocaine/cocaine-framework-go/cocaine12" 9 | ) 10 | 11 | type Response struct { 12 | *bytes.Buffer 13 | closed bool 14 | Err *CocaineError 15 | } 16 | 17 | type CocaineError struct { 18 | Msg string 19 | Code int 20 | } 21 | 22 | var _ cocaine12.Response = NewResponse() 23 | 24 | func NewResponse() *Response { 25 | return &Response{ 26 | Buffer: new(bytes.Buffer), 27 | closed: false, 28 | Err: nil, 29 | } 30 | } 31 | 32 | func (r *Response) Close() error { 33 | if r.closed { 34 | return syscall.EINVAL 35 | } 36 | 37 | r.closed = true 38 | return nil 39 | } 40 | 41 | func (r *Response) ZeroCopyWrite(data []byte) error { 42 | _, err := r.Buffer.Write(data) 43 | return err 44 | } 45 | 46 | func (r *Response) ErrorMsg(code int, msg string) error { 47 | if r.closed { 48 | return io.ErrClosedPipe 49 | } 50 | 51 | r.Err = &CocaineError{ 52 | Msg: msg, 53 | Code: code, 54 | } 55 | return r.Close() 56 | } 57 | -------------------------------------------------------------------------------- /cocaine/sessionkeeper.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type keeperStruct struct { 8 | sync.RWMutex 9 | links map[int64](chan ServiceResult) 10 | counter int64 11 | } 12 | 13 | func newKeeperStruct() *keeperStruct { 14 | return &keeperStruct{links: make(map[int64]chan ServiceResult)} 15 | } 16 | 17 | func (keeper *keeperStruct) Attach(out chan ServiceResult) int64 { 18 | keeper.Lock() 19 | defer keeper.Unlock() 20 | keeper.counter++ 21 | keeper.links[keeper.counter] = out 22 | return keeper.counter 23 | } 24 | 25 | func (keeper *keeperStruct) Detach(id int64) { 26 | keeper.Lock() 27 | defer keeper.Unlock() 28 | delete(keeper.links, id) 29 | } 30 | 31 | func (keeper *keeperStruct) Get(id int64) (ch chan ServiceResult, ok bool) { 32 | keeper.RLock() 33 | defer keeper.RUnlock() 34 | ch, ok = keeper.links[id] 35 | return 36 | } 37 | 38 | func (keeper *keeperStruct) Keys() (keys []int64) { 39 | keeper.RLock() 40 | defer keeper.RUnlock() 41 | for k := range keeper.links { 42 | keys = append(keys, k) 43 | } 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /cocaine12/sessions.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type sessions struct { 8 | sync.RWMutex 9 | links map[uint64]Channel 10 | counter uint64 11 | } 12 | 13 | func newSessions() *sessions { 14 | return &sessions{ 15 | links: make(map[uint64]Channel), 16 | counter: 1, 17 | } 18 | } 19 | 20 | func (s *sessions) Next() uint64 { 21 | s.Lock() 22 | s.counter++ 23 | i := s.counter 24 | s.Unlock() 25 | 26 | return i 27 | } 28 | 29 | func (s *sessions) Attach(session Channel) uint64 { 30 | s.Lock() 31 | 32 | s.counter++ 33 | s.links[s.counter] = session 34 | current := s.counter 35 | 36 | s.Unlock() 37 | return current 38 | } 39 | 40 | func (s *sessions) Detach(id uint64) { 41 | s.Lock() 42 | 43 | delete(s.links, id) 44 | 45 | s.Unlock() 46 | } 47 | 48 | func (s *sessions) Get(id uint64) (Channel, bool) { 49 | s.RLock() 50 | 51 | session, ok := s.links[id] 52 | 53 | s.RUnlock() 54 | return session, ok 55 | } 56 | 57 | func (s *sessions) Keys() []uint64 { 58 | s.RLock() 59 | 60 | var keys = make([]uint64, 0, len(s.links)) 61 | for k := range s.links { 62 | keys = append(keys, k) 63 | } 64 | 65 | s.RUnlock() 66 | return keys 67 | } 68 | -------------------------------------------------------------------------------- /cocaine/http_test.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "net/http" 10 | ) 11 | 12 | func TestUnGzipBody(t *testing.T) { 13 | buffer := bytes.NewBuffer(nil) 14 | buffer.Write([]byte("AAAAAA")) 15 | req, err := http.NewRequest("POST", "/test", buffer) 16 | if err != nil { 17 | t.Fatalf("%v", err) 18 | } 19 | req.Header.Add("Content-Encoding", "gzip") 20 | if err := decompressBody(req); err == nil { 21 | t.Fatal("decompressBody: expect an error") 22 | } 23 | 24 | mock := []byte("ABCDEFGHJKLMNI") 25 | gzipBuffer := bytes.NewBuffer(nil) 26 | gzipWriter := gzip.NewWriter(gzipBuffer) 27 | gzipWriter.Write(mock) 28 | gzipWriter.Close() 29 | 30 | req, err = http.NewRequest("POST", "/test", gzipBuffer) 31 | if err != nil { 32 | t.Fatalf("%v", err) 33 | } 34 | req.Header.Add("Content-Encoding", "gzip") 35 | if err := decompressBody(req); err != nil { 36 | t.Fatalf("decompressBody %v", err) 37 | } 38 | defer req.Body.Close() 39 | 40 | body, err := ioutil.ReadAll(req.Body) 41 | if err != nil { 42 | t.Fatalf("%v", err) 43 | } 44 | 45 | if !bytes.Equal(body, mock) { 46 | t.Fatalf("%s %s", body, mock) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cocaine/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package helps you to write golang application, which can be launched in PaaS Cocaine. 3 | Allows your application to communicate with another applications and services. 4 | 5 | Typical application describes some handlers for incoming events. You have to register handler 6 | for event in special map before starts main eventloop. Look at this example: 7 | 8 | func echo(request *cocaine.Request, response *cocaine.Response) { 9 | inc := <-request.Read() 10 | response.Write(inc) 11 | response.Close() 12 | } 13 | 14 | func main() { 15 | binds := map[string]cocaine.EventHandler{ 16 | "echo": echo, 17 | } 18 | Worker, err := cocaine.NewWorker() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | Worker.Loop(binds) 23 | } 24 | 25 | Incoming event with named "echo" would be handled with echo func. 26 | Request and Response are in/out streams to/from worker. 27 | You are able to read incoming chunks of data, associated with current session using request.Read(). 28 | To send any chunk to client use response.Write(). 29 | Finish datastream by calling response.Close(). 30 | You must close response stream at any time before the end of the handler. 31 | */ 32 | package cocaine 33 | -------------------------------------------------------------------------------- /cocaine12/logger.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import "context" 4 | 5 | const defaultLoggerName = "logging" 6 | 7 | type Fields map[string]interface{} 8 | 9 | type EntryLogger interface { 10 | Errf(format string, args ...interface{}) 11 | Err(args ...interface{}) 12 | 13 | Warnf(format string, args ...interface{}) 14 | Warn(args ...interface{}) 15 | 16 | Infof(format string, args ...interface{}) 17 | Info(args ...interface{}) 18 | 19 | Debugf(format string, args ...interface{}) 20 | Debug(args ...interface{}) 21 | } 22 | 23 | // Logger represents an interface for a cocaine.Logger 24 | type Logger interface { 25 | EntryLogger 26 | 27 | log(level Severity, fields Fields, msg string, args ...interface{}) 28 | WithFields(Fields) *Entry 29 | 30 | Verbosity(context.Context) Severity 31 | V(level Severity) bool 32 | 33 | Close() 34 | } 35 | 36 | var defaultFields = Fields{} 37 | 38 | // NewLogger tries to create a cocaine.Logger. It fallbacks to a simple implementation 39 | // if the cocaine.Logger is unavailable 40 | func NewLogger(ctx context.Context, endpoints ...string) (Logger, error) { 41 | return NewLoggerWithName(ctx, defaultLoggerName, endpoints...) 42 | } 43 | 44 | func NewLoggerWithName(ctx context.Context, name string, endpoints ...string) (Logger, error) { 45 | l, err := newCocaineLogger(ctx, name, endpoints...) 46 | if err != nil { 47 | return newFallbackLogger() 48 | } 49 | return l, nil 50 | } 51 | -------------------------------------------------------------------------------- /cocaine12/entry.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Entry struct { 8 | Logger 9 | Fields Fields 10 | } 11 | 12 | // just for the type check 13 | var _ EntryLogger = &Entry{} 14 | 15 | func (e *Entry) Errf(format string, args ...interface{}) { 16 | if e.V(ErrorLevel) { 17 | e.log(ErrorLevel, e.Fields, format, args...) 18 | } 19 | } 20 | 21 | func (e *Entry) Warnf(format string, args ...interface{}) { 22 | if e.V(WarnLevel) { 23 | e.log(WarnLevel, e.Fields, format, args...) 24 | } 25 | } 26 | 27 | func (e *Entry) Infof(format string, args ...interface{}) { 28 | if e.V(InfoLevel) { 29 | e.log(InfoLevel, e.Fields, format, args...) 30 | } 31 | } 32 | 33 | func (e *Entry) Debugf(format string, args ...interface{}) { 34 | if e.V(DebugLevel) { 35 | e.log(DebugLevel, e.Fields, format, args...) 36 | } 37 | } 38 | 39 | func (e *Entry) Err(args ...interface{}) { 40 | if e.V(ErrorLevel) { 41 | e.log(ErrorLevel, e.Fields, fmt.Sprint(args...)) 42 | } 43 | } 44 | 45 | func (e *Entry) Warn(args ...interface{}) { 46 | if e.V(WarnLevel) { 47 | e.log(WarnLevel, e.Fields, fmt.Sprint(args...)) 48 | } 49 | } 50 | 51 | func (e *Entry) Info(args ...interface{}) { 52 | if e.V(InfoLevel) { 53 | e.log(InfoLevel, e.Fields, fmt.Sprint(args...)) 54 | } 55 | } 56 | 57 | func (e *Entry) Debug(args ...interface{}) { 58 | if e.V(DebugLevel) { 59 | e.log(DebugLevel, e.Fields, fmt.Sprint(args...)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cocaine12/worker_protocol.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | v0 = 0 9 | v1 = 1 10 | ) 11 | 12 | type ErrRequest struct { 13 | Message string 14 | Category, Code int 15 | } 16 | 17 | func (e *ErrRequest) Error() string { 18 | return fmt.Sprintf("[%d] [%d] %s", e.Category, e.Code, e.Message) 19 | } 20 | 21 | type messageTypeDetector interface { 22 | isChunk(msg *Message) bool 23 | } 24 | 25 | type protocolHandler interface { 26 | onChoke(msg *Message) 27 | onChunk(msg *Message) 28 | onError(msg *Message) 29 | onHeartbeat(msg *Message) 30 | onInvoke(msg *Message) error 31 | onTerminate(msg *Message) 32 | } 33 | 34 | type utilityProtocolGenerator interface { 35 | newHandshake(id string) *Message 36 | newHeartbeat() *Message 37 | } 38 | 39 | type handlerProtocolGenerator interface { 40 | messageTypeDetector 41 | newChoke(session uint64) *Message 42 | newChunk(session uint64, data []byte) *Message 43 | newError(session uint64, category, code int, message string) *Message 44 | } 45 | 46 | type protocolDispather interface { 47 | utilityProtocolGenerator 48 | handlerProtocolGenerator 49 | onMessage(p protocolHandler, msg *Message) error 50 | } 51 | 52 | func getEventName(msg *Message) (string, bool) { 53 | switch event := msg.Payload[0].(type) { 54 | case string: 55 | return event, true 56 | case []uint8: 57 | return string(event), true 58 | } 59 | return "", false 60 | } 61 | -------------------------------------------------------------------------------- /cocaine12/dispatch.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type dispatchType int 8 | 9 | const ( 10 | recursiveDispatch dispatchType = iota 11 | emptyDispatch 12 | otherDispatch 13 | ) 14 | 15 | type dispatchMap map[uint64]dispatchItem 16 | 17 | func (d *dispatchMap) Methods() []string { 18 | var methods = make([]string, 0, len(*d)) 19 | for _, v := range *d { 20 | methods = append(methods, v.Name) 21 | } 22 | return methods 23 | } 24 | 25 | func (d *dispatchMap) MethodByName(name string) (uint64, error) { 26 | for i, v := range *d { 27 | if v.Name == name { 28 | return i, nil 29 | } 30 | } 31 | return 0, fmt.Errorf("no `%s` method", name) 32 | } 33 | 34 | type dispatchItem struct { 35 | Name string 36 | Downstream *streamDescription 37 | Upstream *streamDescription 38 | } 39 | 40 | type StreamDescriptionItem struct { 41 | Name string 42 | Description *streamDescription 43 | } 44 | 45 | type streamDescription map[uint64]*StreamDescriptionItem 46 | 47 | func (s *streamDescription) MethodByName(name string) (uint64, error) { 48 | for i, v := range *s { 49 | if v.Name == name { 50 | return i, nil 51 | } 52 | } 53 | 54 | return 0, fmt.Errorf("no `%s` method", name) 55 | } 56 | 57 | func (s *streamDescription) Type() dispatchType { 58 | switch { 59 | case s == nil: 60 | return recursiveDispatch 61 | case len(*s) == 0: 62 | return emptyDispatch 63 | default: 64 | return otherDispatch 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/msgpack.org.md: -------------------------------------------------------------------------------- 1 | **MessagePack and [Binc](http://github.com/ugorji/binc) Codec for [Go](http://golang.org) Language.** 2 | 3 | *A High Performance, Feature-Rich, Idiomatic encode/decode and rpc library*. 4 | 5 | To install: 6 | 7 | go get github.com/ugorji/go/codec 8 | 9 | Source: [http://github.com/ugorji/go] 10 | Online documentation: [http://godoc.org/github.com/ugorji/go/codec] 11 | 12 | Typical usage: 13 | 14 | ```go 15 | // create and use decoder/encoder 16 | var ( 17 | v interface{} // value to decode/encode into 18 | r io.Reader 19 | w io.Writer 20 | b []byte 21 | mh codec.MsgpackHandle 22 | ) 23 | 24 | dec = codec.NewDecoder(r, &mh) 25 | dec = codec.NewDecoderBytes(b, &mh) 26 | err = dec.Decode(&v) 27 | 28 | enc = codec.NewEncoder(w, &mh) 29 | enc = codec.NewEncoderBytes(&b, &mh) 30 | err = enc.Encode(v) 31 | 32 | //RPC Server 33 | go func() { 34 | for { 35 | conn, err := listener.Accept() 36 | rpcCodec := codec.GoRpc.ServerCodec(conn, h) 37 | //OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h) 38 | rpc.ServeCodec(rpcCodec) 39 | } 40 | }() 41 | 42 | //RPC Communication (client side) 43 | conn, err = net.Dial("tcp", "localhost:5555") 44 | rpcCodec := codec.GoRpc.ClientCodec(conn, h) 45 | //OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h) 46 | client := rpc.NewClientWithCodec(rpcCodec) 47 | ``` 48 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, 2013 Ugorji Nwoke. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of the author nor the names of its contributors may be used 13 | to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /cocaine12/workerv1_bench_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func genBullets(sock *asyncRWSocket, bullets uint64) { 10 | for i := uint64(2); i < 2+bullets; i++ { 11 | sock.Write() <- newInvokeV1(i, "echo") 12 | sock.Write() <- newChunkV1(i, []byte("Dummy")) 13 | sock.Write() <- newChokeV1(i) 14 | } 15 | } 16 | 17 | func doBenchmarkWorkerEcho(b *testing.B, bullets uint64) { 18 | const ( 19 | testID = "uuid" 20 | testSession = 10 21 | ) 22 | 23 | in, out := testConn() 24 | sock, _ := newAsyncRW(out) 25 | sock2, _ := newAsyncRW(in) 26 | w, err := newWorker(sock, testID, 1, true) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | w.impl.disownTimer = time.NewTimer(1 * time.Hour) 32 | w.impl.heartbeatTimer = time.NewTimer(1 * time.Hour) 33 | 34 | w.On("echo", func(ctx context.Context, req Request, resp Response) { 35 | defer resp.Close() 36 | 37 | data, err := req.Read(ctx) 38 | if err != nil { 39 | panic(err) 40 | } 41 | resp.Write(data) 42 | }) 43 | 44 | go func() { 45 | w.Run(nil) 46 | }() 47 | defer w.Stop() 48 | 49 | genBullets(sock2, bullets) 50 | 51 | b.ResetTimer() 52 | for i := 0; i < b.N; i++ { 53 | for j := uint64(0); j < bullets; j++ { 54 | // chunk 55 | <-sock2.Read() 56 | // choke 57 | <-sock2.Read() 58 | } 59 | } 60 | } 61 | 62 | func BenchmarkWorkerEcho10(b *testing.B) { 63 | doBenchmarkWorkerEcho(b, 10) 64 | } 65 | 66 | func BenchmarkWorkerEcho100(b *testing.B) { 67 | doBenchmarkWorkerEcho(b, 100) 68 | } 69 | 70 | func BenchmarkWorkerEcho1000(b *testing.B) { 71 | doBenchmarkWorkerEcho(b, 1000) 72 | } 73 | -------------------------------------------------------------------------------- /cocaine12/utils.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | 8 | "github.com/ugorji/go/codec" 9 | ) 10 | 11 | var ( 12 | mPayloadHandler codec.MsgpackHandle 13 | payloadHandler = &mPayloadHandler 14 | ) 15 | 16 | func convertPayload(in interface{}, out interface{}) error { 17 | var buf []byte 18 | if err := codec.NewEncoderBytes(&buf, payloadHandler).Encode(in); err != nil { 19 | return err 20 | } 21 | if err := codec.NewDecoderBytes(buf, payloadHandler).Decode(out); err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | 27 | type ReaderWithContext interface { 28 | io.Reader 29 | SetContext(ctx context.Context) 30 | } 31 | 32 | type requestReader struct { 33 | ctx context.Context 34 | req Request 35 | buffer *bytes.Buffer 36 | } 37 | 38 | func (r *requestReader) Read(p []byte) (int, error) { 39 | // If some data is available but not len(p) bytes, 40 | // Read conventionally returns what is available instead of waiting for more. 41 | if r.buffer.Len() > 0 { 42 | return r.buffer.Read(p) 43 | } 44 | 45 | data, err := r.req.Read(r.ctx) 46 | switch err { 47 | case nil: 48 | // copy the current data to a provided []byte directly 49 | n := copy(p, data) 50 | // if not all the data were copied 51 | // put the rest into a buffer 52 | if n < len(data) { 53 | r.buffer.Write(data[n:]) 54 | } 55 | return n, nil 56 | 57 | case ErrStreamIsClosed: 58 | return 0, io.EOF 59 | 60 | default: 61 | return 0, err 62 | } 63 | } 64 | 65 | func (r *requestReader) SetContext(ctx context.Context) { 66 | r.ctx = ctx 67 | } 68 | 69 | func RequestReader(ctx context.Context, req Request) ReaderWithContext { 70 | return &requestReader{ 71 | ctx: ctx, 72 | req: req, 73 | buffer: new(bytes.Buffer), 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /cocaine12/defaults_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestParseLocators(t *testing.T) { 11 | locatorsV1 := "host1:10053,127.0.0.1:10054,ff:fdf::fdfd:10054" 12 | assert.Equal(t, []string{"host1:10053", "127.0.0.1:10054", "ff:fdf::fdfd:10054"}, parseLocators(locatorsV1)) 13 | locatorsV0 := "localhost:10053" 14 | assert.Equal(t, []string{"localhost:10053"}, parseLocators(locatorsV0)) 15 | } 16 | 17 | func TestParseArgs(t *testing.T) { 18 | args := []string{"--locator", "host1:10053,127.0.0.1:10054", 19 | "--uuid", "uuid", "--protocol", "1", 20 | "--endpoint", "/var/run/cocaine/sock"} 21 | def := newDefaults(args, "test") 22 | assert.Equal(t, 1, def.Protocol(), "invalid protocol version") 23 | assert.Equal(t, "uuid", def.UUID(), "invalid uuid") 24 | assert.Equal(t, "/var/run/cocaine/sock", def.Endpoint(), "invalid endpoint") 25 | assert.Equal(t, []string{"host1:10053", "127.0.0.1:10054"}, def.Locators(), "invalid locators") 26 | } 27 | 28 | func TestParseArgsWithoutLocators(t *testing.T) { 29 | args := []string{} 30 | def := newDefaults(args, "test") 31 | assert.Equal(t, 0, def.Protocol(), "invalid protocol version") 32 | // assert.Equal(t, "uuid", def.UUID(), "invalid uuid") 33 | // assert.Equal(t, "/var/run/cocaine/sock", def.Endpoint(), "invalid endpoint") 34 | assert.Equal(t, []string{"localhost:10053"}, def.Locators(), "invalid locators") 35 | } 36 | 37 | func TestParseToken(t *testing.T) { 38 | os.Setenv("COCAINE_APP_TOKEN_TYPE", "TVM") 39 | os.Setenv("COCAINE_APP_TOKEN_BODY", "very_secret") 40 | 41 | args := []string{} 42 | def := newDefaults(args, "test") 43 | 44 | assert.Equal(t, "TVM", def.Token().Type(), "invalid token type") 45 | assert.Equal(t, "very_secret", def.Token().Body(), "invalid token body") 46 | } 47 | -------------------------------------------------------------------------------- /cocaine12/locator.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // Locator is used to Resolve new services. It should be closed 9 | // after last usage 10 | type Locator interface { 11 | Resolve(ctx context.Context, name string) (*ServiceInfo, error) 12 | Close() 13 | } 14 | 15 | type locator struct { 16 | *Service 17 | } 18 | 19 | // NewLocator creates a new Locator using given endpoints 20 | func NewLocator(endpoints []string) (Locator, error) { 21 | if len(endpoints) == 0 { 22 | endpoints = append(endpoints, GetDefaults().Locators()...) 23 | } 24 | 25 | var ( 26 | sock socketIO 27 | err error 28 | ) 29 | 30 | // ToDo: Duplicated code with Service connection 31 | CONN_LOOP: 32 | for _, endpoint := range endpoints { 33 | sock, err = newAsyncConnection("tcp", endpoint, time.Second*1) 34 | if err != nil { 35 | continue 36 | } 37 | break CONN_LOOP 38 | } 39 | 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | service := Service{ 45 | ServiceInfo: newLocatorServiceInfo(), 46 | socketIO: sock, 47 | sessions: newSessions(), 48 | stop: make(chan struct{}), 49 | args: endpoints, 50 | name: "locator", 51 | } 52 | go service.loop() 53 | 54 | return &locator{ 55 | Service: &service, 56 | }, nil 57 | } 58 | 59 | func (l *locator) Resolve(ctx context.Context, name string) (*ServiceInfo, error) { 60 | channel, err := l.Service.Call(ctx, "resolve", name) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | answer, err := channel.Get(ctx) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | var serviceInfo ServiceInfo 71 | if err := answer.Extract(&serviceInfo); err != nil { 72 | return nil, err 73 | } 74 | 75 | return &serviceInfo, nil 76 | } 77 | 78 | func (l *locator) Close() { 79 | l.socketIO.Close() 80 | } 81 | -------------------------------------------------------------------------------- /cocaine12/worker_handlers.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | // EventHandler represents a type of handler 9 | type EventHandler func(context.Context, Request, Response) 10 | 11 | // RequestHandler represents a type of handler 12 | type RequestHandler func(context.Context, string, Request, Response) 13 | 14 | // TerminationHandler invokes when termination message is received 15 | type TerminationHandler func(context.Context) 16 | 17 | // FallbackEventHandler handles an event if there is no other handler 18 | // for the given event 19 | type FallbackEventHandler RequestHandler 20 | 21 | type EventHandlers struct { 22 | fallback RequestHandler 23 | handlers map[string]EventHandler 24 | } 25 | 26 | func NewEventHandlersFromMap(handlers map[string]EventHandler) *EventHandlers { 27 | return &EventHandlers{DefaultFallbackHandler, handlers} 28 | } 29 | 30 | func NewEventHandlers() *EventHandlers { 31 | return NewEventHandlersFromMap(make(map[string]EventHandler)) 32 | } 33 | 34 | func (e *EventHandlers) On(name string, handler EventHandler) { 35 | e.handlers[name] = handler 36 | } 37 | 38 | // SetFallbackHandler sets the handler to be a fallback handler 39 | func (e *EventHandlers) SetFallbackHandler(handler RequestHandler) { 40 | e.fallback = handler 41 | } 42 | 43 | // DefaultFallbackHandler sends an error message if a client requests 44 | // unhandled event 45 | func DefaultFallbackHandler(ctx context.Context, event string, request Request, response Response) { 46 | errMsg := fmt.Sprintf("There is no handler for an event %s", event) 47 | response.ErrorMsg(ErrorNoEventHandler, errMsg) 48 | } 49 | 50 | func (e *EventHandlers) Call(ctx context.Context, event string, request Request, response Response) { 51 | handler := e.handlers[event] 52 | if handler == nil { 53 | e.fallback(ctx, event, request, response) 54 | return 55 | } 56 | handler(ctx, request, response) 57 | } 58 | -------------------------------------------------------------------------------- /cocaine12/http_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "github.com/ugorji/go/codec" 10 | ) 11 | 12 | var ( 13 | method = "GET" 14 | uri = "/path?a=1" 15 | version = "1.1" 16 | headers = [][2]string{ 17 | [2]string{"Content-Type", "text/html"}, 18 | [2]string{"X-Cocaine-Service", "Test"}, 19 | [2]string{"X-Cocaine-Service", "Test2"}, 20 | } 21 | body = gzipedBody() 22 | req = []interface{}{method, uri, version, headers, body} 23 | ) 24 | 25 | func gzipedBody() []byte { 26 | var b bytes.Buffer 27 | w := gzip.NewWriter(&b) 28 | w.Write([]byte("hello, world\n")) 29 | w.Close() 30 | return b.Bytes() 31 | } 32 | 33 | func packTestReq(req []interface{}) []byte { 34 | var out []byte 35 | codec.NewEncoderBytes(&out, h).Encode(req) 36 | return out 37 | } 38 | 39 | func testUnpackHTTPChunk(payload []interface{}, res interface{}) error { 40 | return codec.NewDecoderBytes(payload[0].([]byte), hHTTPReq).Decode(res) 41 | } 42 | 43 | func TestHTTPDecoder(t *testing.T) { 44 | 45 | var out []byte 46 | if err := codec.NewEncoderBytes(&out, h).Encode(req); err != nil { 47 | t.Fatalf("unable to pack test data: %v", err) 48 | } 49 | 50 | r, err := UnpackProxyRequest(out) 51 | if err != nil { 52 | t.Fatalf("unable to unpack request %v", err) 53 | } 54 | defer r.Body.Close() 55 | 56 | if b, _ := ioutil.ReadAll(r.Body); !bytes.Equal(b, body) { 57 | t.Fatalf("bad bytes: %s %s", b, body) 58 | } 59 | 60 | if r.Method != method { 61 | t.Fatalf("bad method: %s %s", r.Method, method) 62 | } 63 | 64 | if r.Header.Get("X-Cocaine-Service") != "Test" { 65 | t.Fatalf("bad header", r.Header.Get("X-Cocaine-Service")) 66 | } 67 | } 68 | 69 | func BenchmarkHTTPDecoder(b *testing.B) { 70 | var out []byte 71 | codec.NewEncoderBytes(&out, h).Encode(req) 72 | 73 | for n := 0; n < b.N; n++ { 74 | UnpackProxyRequest(out) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /cocaine12/apilocator.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | var ( 4 | emptyDescription = &streamDescription{} 5 | recursiveDescription *streamDescription 6 | ) 7 | 8 | func newLocatorServiceInfo() *ServiceInfo { 9 | return &ServiceInfo{ 10 | Endpoints: nil, 11 | Version: 1, 12 | API: dispatchMap{ 13 | 0: dispatchItem{ 14 | Name: "resolve", 15 | Downstream: emptyDescription, 16 | Upstream: &streamDescription{ 17 | 0: &StreamDescriptionItem{ 18 | Name: "value", 19 | Description: emptyDescription, 20 | }, 21 | 1: &StreamDescriptionItem{ 22 | Name: "error", 23 | Description: emptyDescription, 24 | }, 25 | }, 26 | }, 27 | 1: dispatchItem{ 28 | Name: "connect", 29 | Downstream: emptyDescription, 30 | Upstream: &streamDescription{ 31 | 0: &StreamDescriptionItem{ 32 | Name: "write", 33 | Description: recursiveDescription, 34 | }, 35 | 1: &StreamDescriptionItem{ 36 | Name: "error", 37 | Description: emptyDescription, 38 | }, 39 | 2: &StreamDescriptionItem{ 40 | Name: "close", 41 | Description: emptyDescription, 42 | }, 43 | }, 44 | }, 45 | 2: dispatchItem{ 46 | Name: "refresh", 47 | Downstream: emptyDescription, 48 | Upstream: &streamDescription{ 49 | 0: &StreamDescriptionItem{ 50 | Name: "value", 51 | Description: emptyDescription, 52 | }, 53 | 1: &StreamDescriptionItem{ 54 | Name: "error", 55 | Description: emptyDescription, 56 | }, 57 | }, 58 | }, 59 | 3: dispatchItem{ 60 | Name: "cluster", 61 | Downstream: emptyDescription, 62 | Upstream: &streamDescription{ 63 | 0: &StreamDescriptionItem{ 64 | Name: "value", 65 | Description: emptyDescription, 66 | }, 67 | 1: &StreamDescriptionItem{ 68 | Name: "error", 69 | Description: emptyDescription, 70 | }, 71 | }, 72 | }, 73 | }, 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /cocaine12/utils_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestRequestReaderEOF(t *testing.T) { 13 | type tStruct struct { 14 | L string 15 | N int 16 | } 17 | 18 | ctx := context.Background() 19 | 20 | chunks := []tStruct{ 21 | {"A", 100}, 22 | {"B", 101}, 23 | {"C", 102}, 24 | {"D", 102}, 25 | } 26 | 27 | req := newRequest(newV1Protocol()) 28 | for _, m := range chunks { 29 | body, _ := json.Marshal(m) 30 | req.push(newChunkV1(2, body)) 31 | } 32 | req.Close() 33 | 34 | var actual tStruct 35 | dec := json.NewDecoder(RequestReader(ctx, req)) 36 | for i := range chunks { 37 | err := dec.Decode(&actual) 38 | assert.NoError(t, err) 39 | assert.Equal(t, chunks[i], actual) 40 | } 41 | 42 | err := dec.Decode(&actual) 43 | assert.EqualError(t, err, io.EOF.Error()) 44 | } 45 | 46 | func TestRequestReaderErrorV1(t *testing.T) { 47 | type tStruct struct { 48 | L string 49 | N int 50 | } 51 | 52 | ctx := context.Background() 53 | 54 | chunks := []tStruct{ 55 | {"A", 100}, 56 | {"B", 101}, 57 | {"C", 102}, 58 | {"D", 102}, 59 | } 60 | 61 | req := newRequest(newV1Protocol()) 62 | for _, m := range chunks { 63 | body, _ := json.Marshal(m) 64 | req.push(newChunkV1(2, body)) 65 | } 66 | req.push(newErrorV1(2, 100, 200, "error")) 67 | 68 | var actual tStruct 69 | dec := json.NewDecoder(RequestReader(ctx, req)) 70 | for i := range chunks { 71 | err := dec.Decode(&actual) 72 | assert.NoError(t, err) 73 | assert.Equal(t, chunks[i], actual) 74 | } 75 | 76 | err := dec.Decode(&actual) 77 | expectedV1 := &ErrRequest{"error", 100, 200} 78 | assert.EqualError(t, err, expectedV1.Error()) 79 | } 80 | 81 | func TestServiceResult(t *testing.T) { 82 | sr := serviceRes{ 83 | payload: []interface{}{"A", 100}, 84 | err: nil, 85 | } 86 | 87 | var ( 88 | s string 89 | i int 90 | ) 91 | err := sr.ExtractTuple(&s, &i) 92 | assert.NoError(t, err) 93 | assert.Equal(t, "A", s) 94 | assert.Equal(t, 100, i) 95 | } 96 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/codec/ext_dep_test.go: -------------------------------------------------------------------------------- 1 | //+build ignore 2 | 3 | // Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved. 4 | // Use of this source code is governed by a BSD-style license found in the LICENSE file. 5 | 6 | package codec 7 | 8 | // This file includes benchmarks which have dependencies on 3rdparty 9 | // packages (bson and vmihailenco/msgpack) which must be installed locally. 10 | // 11 | // To run the benchmarks including these 3rdparty packages, first 12 | // - Uncomment first line in this file (put // // in front of it) 13 | // - Get those packages: 14 | // go get github.com/vmihailenco/msgpack 15 | // go get labix.org/v2/mgo/bson 16 | // - Run: 17 | // go test -bi -bench=. 18 | 19 | import ( 20 | "testing" 21 | 22 | vmsgpack "github.com/vmihailenco/msgpack" 23 | "labix.org/v2/mgo/bson" 24 | ) 25 | 26 | func init() { 27 | benchCheckers = append(benchCheckers, 28 | benchChecker{"v-msgpack", fnVMsgpackEncodeFn, fnVMsgpackDecodeFn}, 29 | benchChecker{"bson", fnBsonEncodeFn, fnBsonDecodeFn}, 30 | ) 31 | } 32 | 33 | func fnVMsgpackEncodeFn(ts interface{}) ([]byte, error) { 34 | return vmsgpack.Marshal(ts) 35 | } 36 | 37 | func fnVMsgpackDecodeFn(buf []byte, ts interface{}) error { 38 | return vmsgpack.Unmarshal(buf, ts) 39 | } 40 | 41 | func fnBsonEncodeFn(ts interface{}) ([]byte, error) { 42 | return bson.Marshal(ts) 43 | } 44 | 45 | func fnBsonDecodeFn(buf []byte, ts interface{}) error { 46 | return bson.Unmarshal(buf, ts) 47 | } 48 | 49 | func Benchmark__Bson_______Encode(b *testing.B) { 50 | fnBenchmarkEncode(b, "bson", benchTs, fnBsonEncodeFn) 51 | } 52 | 53 | func Benchmark__Bson_______Decode(b *testing.B) { 54 | fnBenchmarkDecode(b, "bson", benchTs, fnBsonEncodeFn, fnBsonDecodeFn, fnBenchNewTs) 55 | } 56 | 57 | func Benchmark__VMsgpack___Encode(b *testing.B) { 58 | fnBenchmarkEncode(b, "v-msgpack", benchTs, fnVMsgpackEncodeFn) 59 | } 60 | 61 | func Benchmark__VMsgpack___Decode(b *testing.B) { 62 | fnBenchmarkDecode(b, "v-msgpack", benchTs, fnVMsgpackEncodeFn, fnVMsgpackDecodeFn, fnBenchNewTs) 63 | } 64 | 65 | func TestMsgpackPythonGenStreams(t *testing.T) { 66 | doTestMsgpackPythonGenStreams(t) 67 | } 68 | 69 | func TestMsgpackRpcSpecGoClientToPythonSvc(t *testing.T) { 70 | doTestMsgpackRpcSpecGoClientToPythonSvc(t) 71 | } 72 | 73 | func TestMsgpackRpcSpecPythonClientToGoSvc(t *testing.T) { 74 | doTestMsgpackRpcSpecPythonClientToGoSvc(t) 75 | } 76 | -------------------------------------------------------------------------------- /cocaine/locator.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/ugorji/go/codec" 9 | ) 10 | 11 | type Endpoint struct { 12 | Host string 13 | Port int 14 | } 15 | 16 | func (endpoint *Endpoint) AsString() string { 17 | return fmt.Sprintf("%s:%d", endpoint.Host, endpoint.Port) 18 | } 19 | 20 | type ResolveResult struct { 21 | success bool 22 | Endpoint `codec:",omitempty"` 23 | Version int 24 | API map[int64]string 25 | } 26 | 27 | func (r *ResolveResult) getMethodNumber(name string) (number int64, err error) { 28 | for key, value := range r.API { 29 | if value == name { 30 | number = key 31 | return 32 | } 33 | } 34 | err = errors.New("Missing method") 35 | return 36 | } 37 | 38 | type Locator struct { 39 | unpacker *streamUnpacker 40 | socketIO 41 | logger LocalLogger 42 | } 43 | 44 | func NewLocator(logger LocalLogger, args ...interface{}) (*Locator, error) { 45 | endpoint := flagLocator 46 | 47 | if len(args) == 1 { 48 | if _endpoint, ok := args[0].(string); ok { 49 | endpoint = _endpoint 50 | } 51 | } 52 | 53 | sock, err := newAsyncRWSocket("tcp", endpoint, time.Second*5, logger) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return &Locator{newStreamUnpacker(), sock, logger}, nil 58 | } 59 | 60 | func (locator *Locator) unpackchunk(chunk rawMessage) ResolveResult { 61 | var res ResolveResult 62 | err := codec.NewDecoderBytes(chunk, h).Decode(&res) 63 | if err != nil { 64 | locator.logger.Errf("unpack chunk error: %v", err) 65 | } 66 | return res 67 | } 68 | 69 | func (locator *Locator) Resolve(name string) chan ResolveResult { 70 | Out := make(chan ResolveResult) 71 | go func() { 72 | var resolveresult ResolveResult 73 | resolveresult.success = false 74 | msg := ServiceMethod{messageInfo{0, 0}, []interface{}{name}} 75 | locator.socketIO.Write() <- packMsg(&msg) 76 | closed := false 77 | for !closed { 78 | answer := <-locator.socketIO.Read() 79 | msgs := locator.unpacker.Feed(answer, locator.logger) 80 | for _, item := range msgs { 81 | switch id := item.getTypeID(); id { 82 | case CHUNK: 83 | resolveresult = locator.unpackchunk(item.getPayload()[0].([]byte)) 84 | resolveresult.success = true 85 | case CHOKE: 86 | closed = true 87 | } 88 | } 89 | } 90 | Out <- resolveresult 91 | }() 92 | return Out 93 | } 94 | 95 | func (locator *Locator) Close() { 96 | locator.socketIO.Close() 97 | } 98 | -------------------------------------------------------------------------------- /cocaine12/token.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | tmMu sync.RWMutex 10 | factories = make(map[string]TokenManagerFactory) 11 | ) 12 | 13 | // Token carries information about an authorization token. 14 | type Token struct { 15 | ty string 16 | body string 17 | } 18 | 19 | func NewToken(ty string, body string) Token { 20 | return Token{ty, body} 21 | } 22 | 23 | // Type returns token's type. 24 | // Depending on the context it can be empty, OAUTH, TVM etc. 25 | func (t Token) Type() string { 26 | return t.ty 27 | } 28 | 29 | // Body returns token's body. 30 | // The real body meaning can be determined via token's type. 31 | func (t Token) Body() string { 32 | return t.body 33 | } 34 | 35 | type TokenManagerFactory interface { 36 | Create(appName string, token Token) (TokenManager, error) 37 | } 38 | 39 | // TokenManager automates tokens housekeeping, like timely updation to 40 | // be able to always provide the most fresh tokens. 41 | type TokenManager interface { 42 | Token() Token 43 | Stop() 44 | } 45 | 46 | // NullTokenManager represents no-op token manager. 47 | // It always returns an empty token. 48 | type NullTokenManager struct{} 49 | 50 | func (t *NullTokenManager) Token() Token { 51 | return Token{} 52 | } 53 | 54 | func (t *NullTokenManager) Stop() {} 55 | 56 | // TokenManagers returns a sorted list of the names of the registered token 57 | // manager factories. 58 | func TokenManagers() []string { 59 | tmMu.RLock() 60 | defer tmMu.RUnlock() 61 | 62 | var list []string 63 | for name := range factories { 64 | list = append(list, name) 65 | } 66 | sort.Strings(list) 67 | return list 68 | } 69 | 70 | // Register makes a token manager factory available by the provided name. 71 | // If Register is called twice with the same name or if factory 72 | // is nil, it panics. 73 | func Register(name string, factory TokenManagerFactory) { 74 | tmMu.Lock() 75 | defer tmMu.Unlock() 76 | 77 | if factory == nil { 78 | panic("cocaine: TokenManagerFactory is nil") 79 | } 80 | 81 | if _, dup := factories[name]; dup { 82 | panic("cocaine: Register called twice for token manager factory " + name) 83 | } 84 | 85 | factories[name] = factory 86 | } 87 | 88 | func NewTokenManager(appName string, token Token) (TokenManager, error) { 89 | tmMu.Lock() 90 | defer tmMu.Unlock() 91 | 92 | if factory, ok := factories[token.ty]; ok { 93 | return factory.Create(appName, token) 94 | } 95 | 96 | return new(NullTokenManager), nil 97 | } 98 | -------------------------------------------------------------------------------- /cocaine12/worker.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | // Worker performs IO operations between an application 4 | // and cocaine-runtime, dispatches incoming messages 5 | // This is an adapter to WorkerNG 6 | type Worker struct { 7 | impl *WorkerNG 8 | handlers *EventHandlers 9 | terminationHandler TerminationHandler 10 | } 11 | 12 | // NewWorker connects to the cocaine-runtime and create WorkerNG on top of this connection 13 | func NewWorker() (*Worker, error) { 14 | impl, err := NewWorkerNG() 15 | if err != nil { 16 | return nil, err 17 | } 18 | return &Worker{impl, NewEventHandlers(), nil}, nil 19 | } 20 | 21 | // Used in tests only 22 | func newWorker(conn socketIO, id string, protoVersion int, debug bool) (*Worker, error) { 23 | impl, err := newWorkerNG(conn, id, protoVersion, debug, new(NullTokenManager)) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return &Worker{impl, NewEventHandlers(), nil}, nil 28 | } 29 | 30 | // SetDebug enables debug mode of the Worker. 31 | // It allows to print Stack of a paniced handler 32 | func (w *Worker) SetDebug(debug bool) { 33 | w.impl.SetDebug(debug) 34 | } 35 | 36 | // EnableStackSignal allows/disallows the worker to catch 37 | // SIGUSR1 to print all goroutines stacks. It's enabled by default. 38 | // This function must be called before Worker.Run to take effect. 39 | func (w *Worker) EnableStackSignal(enable bool) { 40 | w.impl.EnableStackSignal(enable) 41 | } 42 | 43 | // Token returns the most recently viewed version of the authorization token. 44 | func (w *Worker) Token() Token { 45 | return w.impl.Token() 46 | } 47 | 48 | // SetTerminationHandler allows to attach handler which will be called 49 | // when SIGTERM arrives 50 | func (w *Worker) SetTerminationHandler(handler TerminationHandler) { 51 | w.terminationHandler = handler 52 | } 53 | 54 | // On binds the handler for a given event 55 | func (w *Worker) On(event string, handler EventHandler) { 56 | w.handlers.On(event, handler) 57 | } 58 | 59 | // SetFallbackHandler sets the handler to be a fallback handler 60 | func (w *Worker) SetFallbackHandler(handler FallbackEventHandler) { 61 | w.handlers.SetFallbackHandler(RequestHandler(handler)) 62 | } 63 | 64 | func (w *Worker) Run(handlers map[string]EventHandler) error { 65 | for event, handler := range handlers { 66 | w.On(event, handler) 67 | } 68 | return w.impl.Run(w.handlers.Call, w.terminationHandler) 69 | } 70 | 71 | // Stop makes the Worker stop handling requests 72 | func (w *Worker) Stop() { 73 | w.impl.Stop() 74 | } 75 | -------------------------------------------------------------------------------- /cmd/bridge_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strconv" 7 | 8 | cocaine "github.com/cocaine/cocaine-framework-go/cocaine12" 9 | "github.com/cocaine/cocaine-framework-go/cocaine12/bridge" 10 | "github.com/cocaine/cocaine-framework-go/version" 11 | ) 12 | 13 | func main() { 14 | logger, err := cocaine.NewLogger() 15 | if err != nil { 16 | log.Fatalf("unable to create logger %v", err) 17 | } 18 | defer logger.Close() 19 | 20 | cfg := bridge.NewBridgeConfig() 21 | if cfg.Name = os.Getenv("slave"); cfg.Name == "" { 22 | logger.WithFields( 23 | cocaine.Fields{ 24 | "appsource": "bridge", 25 | "bridgeversion": version.Version, 26 | }).Err("unable to determine a path to the slave") 27 | // to have it in crashlogs 28 | log.Fatal("unable to determine a path to the slave") 29 | } 30 | 31 | if strport := os.Getenv("port"); strport != "" { 32 | // the port is specified 33 | cfg.Port, err = strconv.Atoi(strport) 34 | if err != nil { 35 | logger.WithFields( 36 | cocaine.Fields{ 37 | "appsource": "bridge", 38 | "bridgeversion": version.Version, 39 | }).Errf("unable to determine the port %v", err) 40 | // to have it in crashlogs 41 | log.Fatalf("unable to determine the port %v", err) 42 | } 43 | } 44 | 45 | if strStartupTimeout := os.Getenv("startup-timeout"); strStartupTimeout != "" { 46 | // the port is specified 47 | cfg.StartupTimeout, err = strconv.Atoi(strStartupTimeout) 48 | if err != nil { 49 | logger.WithFields( 50 | cocaine.Fields{ 51 | "appsource": "bridge", 52 | "bridgeversion": version.Version, 53 | }).Errf("unable to determine the startup timeout %v", err) 54 | // to have it in crashlogs 55 | log.Fatalf("unable to determine the startup timeout %v", err) 56 | } 57 | } 58 | 59 | logger.WithFields(cocaine.Fields{ 60 | "appsource": "bridge", 61 | "bridgeversion": version.Version, 62 | "slave": cfg.Name, 63 | "port": cfg.Port, 64 | "startuptimeout": cfg.StartupTimeout, 65 | }).Infof("starting new bridge") 66 | 67 | b, err := bridge.NewBridge(cfg, logger) 68 | if err != nil { 69 | logger.WithFields( 70 | cocaine.Fields{ 71 | "appsource": "bridge", 72 | "bridgeversion": version.Version, 73 | }).Errf("unable to create a bridge %v", err) 74 | log.Fatalf("unable to create bridge %v", err) 75 | } 76 | defer b.Close() 77 | 78 | if err := b.Start(); err != nil { 79 | logger.WithFields( 80 | cocaine.Fields{ 81 | "appsource": "bridge", 82 | "bridgeverson": version.Version, 83 | }).Errf("bridge returned with error: %v", err) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /auth/tvm/tvm.go: -------------------------------------------------------------------------------- 1 | package tvm 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/cocaine/cocaine-framework-go/cocaine12" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | const ( 13 | tvmTokenType = "TVM" 14 | tokenRefreshTimeout = time.Second * 15 15 | ) 16 | 17 | func init() { 18 | cocaine12.Register(tvmTokenType, new(TicketVendingMachineTokenManagerFactory)) 19 | } 20 | 21 | type TicketVendingMachineTokenManagerFactory struct{} 22 | 23 | func (f *TicketVendingMachineTokenManagerFactory) Create(appName string, token cocaine12.Token) (cocaine12.TokenManager, error) { 24 | ctx := context.Background() 25 | tvm, err := cocaine12.NewService(ctx, "tvm", nil) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | ctx, cancel := context.WithCancel(context.Background()) 31 | t := &TicketVendingMachineTokenManager{ 32 | appName: appName, 33 | ticker: time.NewTicker(tokenRefreshTimeout), 34 | ctx: ctx, 35 | cancel: cancel, 36 | mu: sync.Mutex{}, 37 | ticket: token, 38 | tvm: tvm, 39 | } 40 | 41 | go t.run() 42 | 43 | return t, nil 44 | } 45 | 46 | // TicketVendingMachineTokenManager manages TVM tickets. 47 | type TicketVendingMachineTokenManager struct { 48 | appName string 49 | ticker *time.Ticker 50 | ctx context.Context 51 | cancel context.CancelFunc 52 | mu sync.Mutex 53 | ticket cocaine12.Token 54 | tvm *cocaine12.Service 55 | } 56 | 57 | func (t *TicketVendingMachineTokenManager) Token() cocaine12.Token { 58 | t.mu.Lock() 59 | defer t.mu.Unlock() 60 | return t.ticket 61 | } 62 | 63 | func (t *TicketVendingMachineTokenManager) Stop() { 64 | t.cancel() 65 | } 66 | 67 | func (t *TicketVendingMachineTokenManager) run() { 68 | for { 69 | select { 70 | case <-t.ticker.C: 71 | t.mu.Lock() 72 | body := t.ticket.Body() 73 | t.mu.Unlock() 74 | 75 | ch, err := t.tvm.Call(t.ctx, "refresh_ticket", t.appName, body) 76 | if err != nil { 77 | fmt.Printf("failed to update ticket %v\n", err) 78 | continue 79 | } 80 | 81 | answer, err := ch.Get(t.ctx) 82 | if err != nil { 83 | fmt.Printf("failed to get ticket %v\n", err) 84 | continue 85 | } 86 | 87 | var ticketResult string 88 | if err := answer.ExtractTuple(&ticketResult); err != nil { 89 | fmt.Printf("failed to extract ticket %v\n", err) 90 | continue 91 | } 92 | 93 | t.mu.Lock() 94 | t.ticket = cocaine12.NewToken(tvmTokenType, ticketResult) 95 | t.mu.Unlock() 96 | case <-t.ctx.Done(): 97 | t.ticker.Stop() 98 | return 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /cocaine/response_writer.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | ) 7 | 8 | // ResponseWriter implements http.ResponseWriter interface. It implements cocaine integration. 9 | type ResponseWriter struct { 10 | cRes *Response 11 | req *http.Request 12 | handlerHeader http.Header 13 | written int64 // number of bytes written in body 14 | contentLength int64 // explicitly-declared Content-Length; or -1 15 | status int // status code passed to WriteHeader 16 | wroteHeader bool 17 | logger *Logger 18 | } 19 | 20 | 21 | func (w *ResponseWriter) Header() http.Header { 22 | return w.handlerHeader 23 | } 24 | 25 | func (w *ResponseWriter) WriteHeader(code int) { 26 | if w.wroteHeader { 27 | w.logger.Err("http: multiple response.WriteHeader calls") 28 | return 29 | } 30 | w.wroteHeader = true 31 | w.status = code 32 | 33 | if cl := w.handlerHeader.Get("Content-Length"); cl != "" { 34 | v, err := strconv.ParseInt(cl, 10, 64) 35 | if err == nil && v >= 0 { 36 | w.contentLength = v 37 | } else { 38 | w.logger.Errf("http: invalid Content-Length of %q", cl) 39 | w.handlerHeader.Del("Content-Length") 40 | } 41 | } 42 | w.cRes.Write(WriteHead(code, HttpHeaderToCocaineHeader(w.handlerHeader))) 43 | } 44 | 45 | func (w *ResponseWriter) finishRequest() { 46 | if !w.wroteHeader { 47 | w.WriteHeader(http.StatusOK) 48 | } 49 | 50 | if w.req.MultipartForm != nil { 51 | w.req.MultipartForm.RemoveAll() 52 | } 53 | 54 | } 55 | 56 | // bodyAllowed returns true if a Write is allowed for this response type. 57 | // It's illegal to call this before the header has been flushed. 58 | func (w *ResponseWriter) bodyAllowed() bool { 59 | if !w.wroteHeader { 60 | panic("") 61 | } 62 | return w.status != http.StatusNotModified 63 | } 64 | 65 | func (w *ResponseWriter) Write(data []byte) (n int, err error) { 66 | return w.write(len(data), data, "") 67 | } 68 | 69 | func (w *ResponseWriter) WriteString(data string) (n int, err error) { 70 | return w.write(len(data), nil, data) 71 | } 72 | 73 | // either dataB or dataS is non-zero. 74 | func (w *ResponseWriter) write(lenData int, dataB []byte, dataS string) (n int, err error) { 75 | if !w.wroteHeader { 76 | w.WriteHeader(http.StatusOK) 77 | } 78 | if lenData == 0 { 79 | return 0, nil 80 | } 81 | if !w.bodyAllowed() { 82 | return 0, http.ErrBodyNotAllowed 83 | } 84 | 85 | w.written += int64(lenData) // ignoring errors, for errorKludge 86 | if w.contentLength != -1 && w.written > w.contentLength { 87 | return 0, http.ErrContentLength 88 | } 89 | if dataB != nil { 90 | w.cRes.Write(dataB) 91 | return len(dataB), nil 92 | } else { 93 | w.cRes.Write(dataS) 94 | return len(dataS), nil 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /cocaine12/fallbacklogger.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "log" 8 | ) 9 | 10 | type fallbackLogger struct { 11 | severity Severity 12 | } 13 | 14 | func newFallbackLogger(args ...string) (Logger, error) { 15 | return &fallbackLogger{ 16 | severity: DebugLevel, 17 | }, nil 18 | } 19 | 20 | func (f *fallbackLogger) WithFields(fields Fields) *Entry { 21 | return &Entry{ 22 | Logger: f, 23 | Fields: fields, 24 | } 25 | } 26 | 27 | func (f *fallbackLogger) formatFields(fields Fields) string { 28 | if len(fields) == 0 { 29 | return "[ ]" 30 | } 31 | 32 | var b bytes.Buffer 33 | b.WriteByte('[') 34 | b.WriteByte(' ') 35 | for k, v := range fields { 36 | b.WriteString(k) 37 | b.WriteByte('=') 38 | b.WriteString(fmt.Sprint(v)) 39 | b.WriteByte(' ') 40 | } 41 | b.WriteByte(']') 42 | 43 | return b.String() 44 | } 45 | 46 | func (f *fallbackLogger) V(level Severity) bool { 47 | return level >= f.severity.get() 48 | } 49 | 50 | func (f *fallbackLogger) log(level Severity, fields Fields, msg string, args ...interface{}) { 51 | if !f.V(level) { 52 | return 53 | } 54 | 55 | if len(fields) == 0 { 56 | log.Printf("[%s] %s", level.String(), fmt.Sprintf(msg, args...)) 57 | } else { 58 | log.Printf("[%s] %s %s", level.String(), fmt.Sprintf(msg, args...), f.formatFields(fields)) 59 | } 60 | } 61 | 62 | func (f *fallbackLogger) Errf(format string, args ...interface{}) { 63 | f.log(ErrorLevel, defaultFields, format, args...) 64 | } 65 | 66 | func (f *fallbackLogger) Err(args ...interface{}) { 67 | f.log(ErrorLevel, defaultFields, fmt.Sprint(args...)) 68 | } 69 | 70 | func (f *fallbackLogger) Warnf(format string, args ...interface{}) { 71 | f.log(WarnLevel, defaultFields, format, args...) 72 | } 73 | 74 | func (f *fallbackLogger) Warn(args ...interface{}) { 75 | f.log(WarnLevel, defaultFields, fmt.Sprint(args...)) 76 | } 77 | 78 | func (f *fallbackLogger) Infof(format string, args ...interface{}) { 79 | f.log(InfoLevel, defaultFields, format, args...) 80 | } 81 | 82 | func (f *fallbackLogger) Info(args ...interface{}) { 83 | f.log(InfoLevel, defaultFields, fmt.Sprint(args...)) 84 | } 85 | 86 | func (f *fallbackLogger) Debugf(format string, args ...interface{}) { 87 | f.log(DebugLevel, defaultFields, format, args...) 88 | } 89 | 90 | func (f *fallbackLogger) Debug(args ...interface{}) { 91 | f.log(DebugLevel, defaultFields, fmt.Sprint(args...)) 92 | } 93 | 94 | func (f *fallbackLogger) Verbosity(context.Context) Severity { 95 | return f.severity.get() 96 | } 97 | 98 | func (f *fallbackLogger) SetVerbosity(value Severity) { 99 | f.severity.set(value) 100 | } 101 | 102 | func (f *fallbackLogger) Close() { 103 | } 104 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/codec/z_helper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license found in the LICENSE file. 3 | 4 | package codec 5 | 6 | // All non-std package dependencies related to testing live in this file, 7 | // so porting to different environment is easy (just update functions). 8 | // 9 | // Also, this file is called z_helper_test, to give a "hint" to compiler 10 | // that its init() function should be called last. (not guaranteed by spec) 11 | 12 | import ( 13 | "errors" 14 | "reflect" 15 | "flag" 16 | "testing" 17 | ) 18 | 19 | var ( 20 | testLogToT = true 21 | failNowOnFail = true 22 | ) 23 | 24 | func init() { 25 | testInitFlags() 26 | benchInitFlags() 27 | flag.Parse() 28 | testInit() 29 | benchInit() 30 | } 31 | 32 | func checkErrT(t *testing.T, err error) { 33 | if err != nil { 34 | logT(t, err.Error()) 35 | failT(t) 36 | } 37 | } 38 | 39 | func checkEqualT(t *testing.T, v1 interface{}, v2 interface{}, desc string) (err error) { 40 | if err = deepEqual(v1, v2); err != nil { 41 | logT(t, "Not Equal: %s: %v. v1: %v, v2: %v", desc, err, v1, v2) 42 | failT(t) 43 | } 44 | return 45 | } 46 | 47 | func logT(x interface{}, format string, args ...interface{}) { 48 | if t, ok := x.(*testing.T); ok && t != nil && testLogToT { 49 | t.Logf(format, args...) 50 | } else if b, ok := x.(*testing.B); ok && b != nil && testLogToT { 51 | b.Logf(format, args...) 52 | } else { 53 | debugf(format, args...) 54 | } 55 | } 56 | 57 | func failT(t *testing.T) { 58 | if failNowOnFail { 59 | t.FailNow() 60 | } else { 61 | t.Fail() 62 | } 63 | } 64 | 65 | func deepEqual(v1, v2 interface{}) (err error) { 66 | if !reflect.DeepEqual(v1, v2) { 67 | err = errors.New("Not Match") 68 | } 69 | return 70 | } 71 | 72 | func approxDataSize(rv reflect.Value) (sum int) { 73 | switch rk := rv.Kind(); rk { 74 | case reflect.Invalid: 75 | case reflect.Ptr, reflect.Interface: 76 | sum += int(rv.Type().Size()) 77 | sum += approxDataSize(rv.Elem()) 78 | case reflect.Slice: 79 | sum += int(rv.Type().Size()) 80 | for j := 0; j < rv.Len(); j++ { 81 | sum += approxDataSize(rv.Index(j)) 82 | } 83 | case reflect.String: 84 | sum += int(rv.Type().Size()) 85 | sum += rv.Len() 86 | case reflect.Map: 87 | sum += int(rv.Type().Size()) 88 | for _, mk := range rv.MapKeys() { 89 | sum += approxDataSize(mk) 90 | sum += approxDataSize(rv.MapIndex(mk)) 91 | } 92 | case reflect.Struct: 93 | //struct size already includes the full data size. 94 | //sum += int(rv.Type().Size()) 95 | for j := 0; j < rv.NumField(); j++ { 96 | sum += approxDataSize(rv.Field(j)) 97 | } 98 | default: 99 | //pure value types 100 | sum += int(rv.Type().Size()) 101 | } 102 | return 103 | } 104 | -------------------------------------------------------------------------------- /cocaine12/httpresponse.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | ) 7 | 8 | // ResponseWriter implements http.ResponseWriter interface. 9 | // It implements cocaine integration. 10 | type ResponseWriter struct { 11 | cRes ResponseStream 12 | req *http.Request 13 | handlerHeader http.Header 14 | // number of bytes written in body 15 | written int64 16 | // explicitly-declared Content-Length; or -1 17 | contentLength int64 18 | // status code passed to WriteHeader 19 | status int 20 | wroteHeader bool 21 | } 22 | 23 | // Header returns the header map that will be sent by WriteHeader 24 | func (w *ResponseWriter) Header() http.Header { 25 | return w.handlerHeader 26 | } 27 | 28 | // WriteHeader sends an HTTP response header with status code 29 | func (w *ResponseWriter) WriteHeader(code int) { 30 | if w.wroteHeader { 31 | return 32 | } 33 | 34 | w.wroteHeader = true 35 | w.status = code 36 | 37 | if cl := w.handlerHeader.Get("Content-Length"); cl != "" { 38 | if v, err := strconv.ParseInt(cl, 10, 64); err == nil && v >= 0 { 39 | w.contentLength = v 40 | } else { 41 | w.handlerHeader.Del("Content-Length") 42 | } 43 | } 44 | 45 | w.cRes.ZeroCopyWrite( 46 | WriteHead(code, HeadersHTTPtoCocaine(w.handlerHeader)), 47 | ) 48 | } 49 | 50 | func (w *ResponseWriter) finishRequest() { 51 | if !w.wroteHeader { 52 | w.WriteHeader(http.StatusOK) 53 | } 54 | 55 | if w.req.MultipartForm != nil { 56 | w.req.MultipartForm.RemoveAll() 57 | } 58 | 59 | } 60 | 61 | // bodyAllowed returns true if a Write is allowed for this response type. 62 | // It's illegal to call this before the header has been flushed. 63 | func (w *ResponseWriter) bodyAllowed() bool { 64 | if !w.wroteHeader { 65 | panic("") 66 | } 67 | 68 | return w.status != http.StatusNotModified 69 | } 70 | 71 | // Write writes the data to the connection as part of an HTTP reply 72 | func (w *ResponseWriter) Write(data []byte) (n int, err error) { 73 | return w.write(data, true) 74 | } 75 | 76 | // WriteString writes the string to the connection as part of an HTTP reply 77 | func (w *ResponseWriter) WriteString(data string) (n int, err error) { 78 | // Converting from string to []byte copied the underlying buffer, 79 | // so write can avoid copying. 80 | return w.write([]byte(data), false) 81 | } 82 | 83 | func (w *ResponseWriter) write(data []byte, shouldCopy bool) (n int, err error) { 84 | if !w.wroteHeader { 85 | w.WriteHeader(http.StatusOK) 86 | } 87 | 88 | if len(data) == 0 { 89 | return 0, nil 90 | } 91 | 92 | if !w.bodyAllowed() { 93 | return 0, http.ErrBodyNotAllowed 94 | } 95 | 96 | w.written += int64(len(data)) // ignoring errors, for errorKludge 97 | if w.contentLength != -1 && w.written > w.contentLength { 98 | return 0, http.ErrContentLength 99 | } 100 | 101 | if shouldCopy { 102 | w.cRes.Write(data) 103 | } else { 104 | w.cRes.ZeroCopyWrite(data) 105 | } 106 | 107 | return len(data), nil 108 | } 109 | -------------------------------------------------------------------------------- /Bridge.md: -------------------------------------------------------------------------------- 1 | # Bridge proxy 2 | 3 | ## Goal 4 | 5 | Bridge is a convenient way to lauch web applications in Cocaine Cloud without any changes 6 | it the application code for testing purposes. 7 | But it provides a limited support of the platform power right now. 8 | For example, it does not support services, so you have to use a framework for your language to 9 | work with services. 10 | 11 | ## Configuration 12 | 13 | Bridge is configurated by environment variables. The best place for such options is a manifest. 14 | Also the options could be set in Dockerfile. Let's look at an example: 15 | 16 | ```json 17 | { 18 | "environment": { 19 | "slave": "./app.py", // path to your application 20 | "port": "8080", // port which is listened by your app (default: "8080") 21 | "startup-timeout": "5" 22 | }, 23 | 24 | "slave": "/usr/bin/bridge" // path to bridge binary inside a container 25 | } 26 | 27 | ``` 28 | 29 | Bridge supports folowing options: 30 | + *slave* - this a path to your actual application inside container 31 | + *port* - a port which is listened by your application 32 | + *startup-timeout* - timeout for pinging your app before accepts load 33 | 34 | ## Usage 35 | 36 | To use Bridge you have to copy its binary inside a container during a build phase. 37 | For example, let's look at a part of Dockerfile that copies the binary: 38 | 39 | ``` 40 | # it copies the binary from a Docker context dir into a container 41 | ADD ./bridge /usr/bin/bridge 42 | ``` 43 | 44 | In manifest `slave` option has to point out to a bridge binary. 45 | 46 | 47 | ## How it works 48 | 49 | ### Requests 50 | 51 | Bridge works like a proxy between cocaine-runtime and your app. It delivers HTTP requests from users 52 | encoded to Cocaine protocol, decodes them and delivers to your web app. Of course, it transfers them back to users. 53 | 54 | Bridge starts sending requests to your application after successful connection to your port. 55 | 56 | ``` 57 | COCAINE HTTP REQUEST HTTP REQUEST COCAINE RESPONSE 58 | ========================================================================================================== 59 | [METHOD, URI, VERSION, HEADERS, BODY] ---> HTTP RESPONSE ---> [STATUS CODE, HEADERS], [BODY], [BODY] 60 | ``` 61 | 62 | ### Logs 63 | 64 | Bridge collects `stdout/stderr` of the application and logs both of them by Cocaine logging service. 65 | `stderr` is logged using **ERROR** level, `stdout` using **INFO**. 66 | 67 | ### Overwatch 68 | 69 | Bridge keeps an eye on your application. If the application crashes, Bridge sends an error message about it to Cocaine and 70 | dies too. 71 | If Cocaine decides to kill the application, Bridge sends a SIGTERM to your application, waits *5* seconds for a termination. 72 | If your app is still alive it would be killed by SIGKILL. If bridge dies, you receive a SIGKILL too (via *pdeathsig*). 73 | Before delivering a load your application is being pinged for `startup-timeout` to find out that you are ready to work. 74 | Simple TCP-check is used. An successfully established connection to your port means that it's time to work. 75 | 76 | 77 | ## Build 78 | 79 | ```make bridge``` 80 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/codec/helper_internal.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license found in the LICENSE file. 3 | 4 | package codec 5 | 6 | // All non-std package dependencies live in this file, 7 | // so porting to different environment is easy (just update functions). 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "math" 13 | "reflect" 14 | ) 15 | 16 | var ( 17 | raisePanicAfterRecover = false 18 | debugging = true 19 | ) 20 | 21 | func panicValToErr(panicVal interface{}, err *error) { 22 | switch xerr := panicVal.(type) { 23 | case error: 24 | *err = xerr 25 | case string: 26 | *err = errors.New(xerr) 27 | default: 28 | *err = fmt.Errorf("%v", panicVal) 29 | } 30 | if raisePanicAfterRecover { 31 | panic(panicVal) 32 | } 33 | return 34 | } 35 | 36 | func isEmptyValueDeref(v reflect.Value, deref bool) bool { 37 | switch v.Kind() { 38 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 39 | return v.Len() == 0 40 | case reflect.Bool: 41 | return !v.Bool() 42 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 43 | return v.Int() == 0 44 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 45 | return v.Uint() == 0 46 | case reflect.Float32, reflect.Float64: 47 | return v.Float() == 0 48 | case reflect.Interface, reflect.Ptr: 49 | if deref { 50 | if v.IsNil() { 51 | return true 52 | } 53 | return isEmptyValueDeref(v.Elem(), deref) 54 | } else { 55 | return v.IsNil() 56 | } 57 | case reflect.Struct: 58 | // return true if all fields are empty. else return false. 59 | 60 | // we cannot use equality check, because some fields may be maps/slices/etc 61 | // and consequently the structs are not comparable. 62 | // return v.Interface() == reflect.Zero(v.Type()).Interface() 63 | for i, n := 0, v.NumField(); i < n; i++ { 64 | if !isEmptyValueDeref(v.Field(i), deref) { 65 | return false 66 | } 67 | } 68 | return true 69 | } 70 | return false 71 | } 72 | 73 | func isEmptyValue(v reflect.Value) bool { 74 | return isEmptyValueDeref(v, true) 75 | } 76 | 77 | func debugf(format string, args ...interface{}) { 78 | if debugging { 79 | if len(format) == 0 || format[len(format)-1] != '\n' { 80 | format = format + "\n" 81 | } 82 | fmt.Printf(format, args...) 83 | } 84 | } 85 | 86 | func pruneSignExt(v []byte, pos bool) (n int) { 87 | if len(v) < 2 { 88 | } else if pos && v[0] == 0 { 89 | for ; v[n] == 0 && n+1 < len(v) && (v[n+1]&(1<<7) == 0); n++ { 90 | } 91 | } else if !pos && v[0] == 0xff { 92 | for ; v[n] == 0xff && n+1 < len(v) && (v[n+1]&(1<<7) != 0); n++ { 93 | } 94 | } 95 | return 96 | } 97 | 98 | func implementsIntf(typ, iTyp reflect.Type) (success bool, indir int8) { 99 | if typ == nil { 100 | return 101 | } 102 | rt := typ 103 | // The type might be a pointer and we need to keep 104 | // dereferencing to the base type until we find an implementation. 105 | for { 106 | if rt.Implements(iTyp) { 107 | return true, indir 108 | } 109 | if p := rt; p.Kind() == reflect.Ptr { 110 | indir++ 111 | if indir >= math.MaxInt8 { // insane number of indirections 112 | return false, 0 113 | } 114 | rt = p.Elem() 115 | continue 116 | } 117 | break 118 | } 119 | // No luck yet, but if this is a base type (non-pointer), the pointer might satisfy. 120 | if typ.Kind() != reflect.Ptr { 121 | // Not a pointer, but does the pointer work? 122 | if reflect.PtrTo(typ).Implements(iTyp) { 123 | return true, -1 124 | } 125 | } 126 | return false, 0 127 | } 128 | -------------------------------------------------------------------------------- /cocaine12/defaults.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | const ( 13 | defaultProtocolVersion = 0 14 | defaultLocatorEndpoint = "localhost:10053" 15 | tokenTypeKey = "COCAINE_APP_TOKEN_TYPE" 16 | tokenBodyKey = "COCAINE_APP_TOKEN_BODY" 17 | ) 18 | 19 | type defaultValues struct { 20 | appName string 21 | endpoint string 22 | locators locatorsType 23 | protocol int 24 | uuid string 25 | debug bool 26 | token Token 27 | } 28 | 29 | func (d *defaultValues) ApplicationName() string { 30 | return d.appName 31 | } 32 | 33 | func (d *defaultValues) Endpoint() string { 34 | return d.endpoint 35 | } 36 | 37 | func (d *defaultValues) Debug() bool { 38 | return d.debug 39 | } 40 | 41 | func (d *defaultValues) Locators() []string { 42 | return d.locators 43 | } 44 | 45 | func (d *defaultValues) Protocol() int { 46 | return d.protocol 47 | } 48 | 49 | func (d *defaultValues) UUID() string { 50 | return d.uuid 51 | } 52 | 53 | func (d *defaultValues) DC() string { 54 | // TODO(mechmind): return real DC when cocaine runtime will support this 55 | // falling back to "global" if dc location is not available 56 | return "global" 57 | } 58 | 59 | func (d *defaultValues) Token() Token { 60 | return d.token 61 | } 62 | 63 | // DefaultValues provides an interface to read 64 | // various information provided by Cocaine-Runtime to the worker 65 | type DefaultValues interface { 66 | ApplicationName() string 67 | Debug() bool 68 | Endpoint() string 69 | Locators() []string 70 | Protocol() int 71 | UUID() string 72 | DC() string 73 | Token() Token 74 | } 75 | 76 | var ( 77 | initDefaultValues sync.Once 78 | storedDefaults DefaultValues 79 | 80 | parseDefaultValues = func() { 81 | storedDefaults = newDefaults(os.Args[1:], "cocaine") 82 | } 83 | ) 84 | 85 | // GetDefaults returns DefaultValues 86 | func GetDefaults() DefaultValues { 87 | // lazy init 88 | initDefaultValues.Do(parseDefaultValues) 89 | 90 | return storedDefaults 91 | } 92 | 93 | type locatorsType []string 94 | 95 | func (l *locatorsType) Set(value string) error { 96 | (*l) = parseLocators(value) 97 | return nil 98 | } 99 | 100 | func (l *locatorsType) String() string { 101 | return strings.Join((*l), ",") 102 | } 103 | 104 | func parseLocators(arg string) []string { 105 | if strings.IndexRune(arg, ',') == -1 { 106 | return []string{arg} 107 | } 108 | 109 | return strings.Split(arg, ",") 110 | } 111 | 112 | func newDefaults(args []string, setname string) *defaultValues { 113 | var ( 114 | values = new(defaultValues) 115 | 116 | showVersion bool 117 | ) 118 | 119 | values.locators = []string{defaultLocatorEndpoint} 120 | values.debug = strings.ToUpper(os.Getenv("DEBUG")) == "DEBUG" 121 | 122 | flagSet := flag.NewFlagSet(setname, flag.ContinueOnError) 123 | flagSet.SetOutput(ioutil.Discard) 124 | flagSet.StringVar(&values.appName, "app", "gostandalone", "application name") 125 | flagSet.StringVar(&values.endpoint, "endpoint", "", "unix socket path to connect to the Cocaine") 126 | flagSet.Var(&values.locators, "locator", "default endpoints of locators") 127 | flagSet.IntVar(&values.protocol, "protocol", defaultProtocolVersion, "protocol version") 128 | flagSet.StringVar(&values.uuid, "uuid", "", "UUID") 129 | flagSet.BoolVar(&showVersion, "showcocaineversion", false, "print framework version") 130 | flagSet.Parse(args) 131 | 132 | values.token = Token{os.Getenv(tokenTypeKey), os.Getenv(tokenBodyKey)} 133 | 134 | if showVersion { 135 | fmt.Fprintf(os.Stderr, "Built with Cocaine framework %s\n", frameworkVersion) 136 | os.Exit(0) 137 | } 138 | 139 | return values 140 | } 141 | -------------------------------------------------------------------------------- /cocaine12/wprotov1.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | v1Handshake = 0 9 | v1Heartbeat = 0 10 | v1Invoke = 0 11 | v1Write = 0 12 | v1Error = 1 13 | v1Close = 2 14 | v1Terminate = 1 15 | 16 | v1UtilitySession = 1 17 | ) 18 | 19 | type v1Protocol struct { 20 | maxSession uint64 21 | } 22 | 23 | func newV1Protocol() protocolDispather { 24 | return &v1Protocol{ 25 | maxSession: 1, 26 | } 27 | } 28 | 29 | func (v *v1Protocol) onMessage(p protocolHandler, msg *Message) error { 30 | if msg.Session == v1UtilitySession { 31 | return v.dispatchUtilityMessage(p, msg) 32 | } 33 | 34 | if v.maxSession < msg.Session { 35 | // It must be Invkoke 36 | if msg.MsgType != v1Invoke { 37 | return fmt.Errorf("new session %d must start from invoke type %d, not %d\n", 38 | msg.Session, v1Invoke, msg.MsgType) 39 | } 40 | 41 | v.maxSession = msg.Session 42 | return p.onInvoke(msg) 43 | } 44 | 45 | switch msg.MsgType { 46 | case v1Write: 47 | p.onChunk(msg) 48 | case v1Close: 49 | p.onChoke(msg) 50 | case v1Error: 51 | p.onError(msg) 52 | default: 53 | return fmt.Errorf("an invalid message type: %d, message %v", msg.MsgType, msg) 54 | } 55 | return nil 56 | } 57 | 58 | func (v *v1Protocol) isChunk(msg *Message) bool { 59 | return msg.MsgType == v1Write 60 | } 61 | 62 | func (v *v1Protocol) dispatchUtilityMessage(p protocolHandler, msg *Message) error { 63 | switch msg.MsgType { 64 | case v1Heartbeat: 65 | p.onHeartbeat(msg) 66 | case v1Terminate: 67 | p.onTerminate(msg) 68 | default: 69 | return fmt.Errorf("an invalid utility message type %d", msg.MsgType) 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func (v *v1Protocol) newHandshake(id string) *Message { 76 | return newHandshakeV1(id) 77 | } 78 | 79 | func (v *v1Protocol) newHeartbeat() *Message { 80 | return newHeartbeatV1() 81 | } 82 | 83 | func (v *v1Protocol) newChoke(session uint64) *Message { 84 | return newChokeV1(session) 85 | } 86 | 87 | func (v *v1Protocol) newChunk(session uint64, data []byte) *Message { 88 | return newChunkV1(session, data) 89 | } 90 | 91 | func (v *v1Protocol) newError(session uint64, category, code int, message string) *Message { 92 | return newErrorV1(session, category, code, message) 93 | } 94 | 95 | func newHandshakeV1(id string) *Message { 96 | return &Message{ 97 | CommonMessageInfo: CommonMessageInfo{ 98 | Session: v1UtilitySession, 99 | MsgType: v1Handshake, 100 | }, 101 | Payload: []interface{}{id}, 102 | } 103 | } 104 | 105 | func newHeartbeatV1() *Message { 106 | return &Message{ 107 | CommonMessageInfo: CommonMessageInfo{ 108 | Session: v1UtilitySession, 109 | MsgType: v1Heartbeat, 110 | }, 111 | Payload: []interface{}{}, 112 | } 113 | } 114 | 115 | func newInvokeV1(session uint64, event string) *Message { 116 | return &Message{ 117 | CommonMessageInfo: CommonMessageInfo{ 118 | Session: session, 119 | MsgType: v1Invoke, 120 | }, 121 | Payload: []interface{}{event}, 122 | } 123 | } 124 | 125 | func newChunkV1(session uint64, data []byte) *Message { 126 | return &Message{ 127 | CommonMessageInfo: CommonMessageInfo{ 128 | Session: session, 129 | MsgType: v1Write, 130 | }, 131 | Payload: []interface{}{data}, 132 | } 133 | } 134 | 135 | func newErrorV1(session uint64, category, code int, message string) *Message { 136 | return &Message{ 137 | CommonMessageInfo: CommonMessageInfo{ 138 | Session: session, 139 | MsgType: v1Error, 140 | }, 141 | Payload: []interface{}{[2]int{category, code}, message}, 142 | } 143 | } 144 | 145 | func newChokeV1(session uint64) *Message { 146 | return &Message{ 147 | CommonMessageInfo: CommonMessageInfo{ 148 | Session: session, 149 | MsgType: v1Close, 150 | }, 151 | Payload: []interface{}{}, 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /cocaine12/clogger.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | const loggerEmit = 0 10 | 11 | type cocaineLogger struct { 12 | *Service 13 | 14 | mu sync.Mutex 15 | severity Severity 16 | prefix string 17 | } 18 | 19 | type attrPair struct { 20 | Name string 21 | Value interface{} 22 | } 23 | 24 | func formatFields(f Fields) []attrPair { 25 | formatted := make([]attrPair, 0, len(f)) 26 | for k, v := range f { 27 | formatted = append(formatted, attrPair{k, v}) 28 | } 29 | 30 | return formatted 31 | } 32 | 33 | func newCocaineLogger(ctx context.Context, name string, endpoints ...string) (Logger, error) { 34 | service, err := NewService(ctx, name, endpoints) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | logger := &cocaineLogger{ 40 | Service: service, 41 | severity: -100, 42 | prefix: fmt.Sprintf("app/%s", GetDefaults().ApplicationName()), 43 | } 44 | 45 | return logger, nil 46 | } 47 | 48 | func (c *cocaineLogger) Close() { 49 | c.Service.Close() 50 | } 51 | 52 | func (c *cocaineLogger) Verbosity(ctx context.Context) (level Severity) { 53 | level = DebugLevel 54 | if lvl := c.severity.get(); lvl != -100 { 55 | return lvl 56 | } 57 | 58 | channel, err := c.Service.Call(ctx, "verbosity") 59 | if err != nil { 60 | return 61 | } 62 | 63 | result, err := channel.Get(ctx) 64 | if err != nil { 65 | return 66 | } 67 | 68 | var verbosity struct { 69 | Level Severity 70 | } 71 | 72 | err = result.Extract(&verbosity) 73 | if err != nil { 74 | return 75 | } 76 | 77 | c.severity.set(verbosity.Level) 78 | 79 | return verbosity.Level 80 | } 81 | 82 | func (c *cocaineLogger) V(level Severity) bool { 83 | return level >= c.severity.get() 84 | } 85 | 86 | func (c *cocaineLogger) WithFields(fields Fields) *Entry { 87 | return &Entry{ 88 | Logger: c, 89 | Fields: fields, 90 | } 91 | } 92 | 93 | func (c *cocaineLogger) log(level Severity, fields Fields, msg string, args ...interface{}) { 94 | var methodArgs []interface{} 95 | if len(args) > 0 { 96 | methodArgs = []interface{}{level, c.prefix, fmt.Sprintf(msg, args...), formatFields(fields)} 97 | } else { 98 | methodArgs = []interface{}{level, c.prefix, msg, formatFields(fields)} 99 | } 100 | 101 | c.mu.Lock() 102 | defer c.mu.Unlock() 103 | 104 | loggermsg := &Message{ 105 | CommonMessageInfo: CommonMessageInfo{c.Service.sessions.Next(), loggerEmit}, 106 | Payload: methodArgs, 107 | } 108 | 109 | c.Service.sendMsg(loggermsg) 110 | } 111 | 112 | func (c *cocaineLogger) Debug(args ...interface{}) { 113 | if c.V(DebugLevel) { 114 | c.log(DebugLevel, defaultFields, fmt.Sprint(args...)) 115 | } 116 | } 117 | 118 | func (c *cocaineLogger) Debugf(msg string, args ...interface{}) { 119 | if c.V(DebugLevel) { 120 | c.log(DebugLevel, defaultFields, msg, args...) 121 | } 122 | } 123 | 124 | func (c *cocaineLogger) Info(args ...interface{}) { 125 | if c.V(InfoLevel) { 126 | c.log(InfoLevel, defaultFields, fmt.Sprint(args...)) 127 | } 128 | } 129 | 130 | func (c *cocaineLogger) Infof(msg string, args ...interface{}) { 131 | if c.V(InfoLevel) { 132 | c.log(InfoLevel, defaultFields, msg, args...) 133 | } 134 | } 135 | 136 | func (c *cocaineLogger) Warn(args ...interface{}) { 137 | if c.V(WarnLevel) { 138 | c.log(WarnLevel, defaultFields, fmt.Sprint(args...)) 139 | } 140 | } 141 | 142 | func (c *cocaineLogger) Warnf(msg string, args ...interface{}) { 143 | if c.V(WarnLevel) { 144 | c.log(WarnLevel, defaultFields, msg, args...) 145 | } 146 | } 147 | 148 | func (c *cocaineLogger) Err(args ...interface{}) { 149 | if c.V(ErrorLevel) { 150 | c.log(ErrorLevel, defaultFields, fmt.Sprint(args...)) 151 | } 152 | } 153 | 154 | func (c *cocaineLogger) Errf(msg string, args ...interface{}) { 155 | if c.V(ErrorLevel) { 156 | c.log(ErrorLevel, defaultFields, msg, args...) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /cocaine12/channel.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | type Channel interface { 10 | Rx 11 | Tx 12 | } 13 | 14 | type Rx interface { 15 | Get(context.Context) (ServiceResult, error) 16 | Closed() bool 17 | push(ServiceResult) 18 | } 19 | 20 | type Tx interface { 21 | Call(ctx context.Context, name string, args ...interface{}) error 22 | } 23 | 24 | type channel struct { 25 | // we call when data frame arrives 26 | traceReceived CloseSpan 27 | // we call when data is sent 28 | traceSent CloseSpan 29 | 30 | rx 31 | tx 32 | } 33 | 34 | func (ch *channel) push(res ServiceResult) { 35 | ch.traceReceived() 36 | ch.rx.push(res) 37 | } 38 | 39 | func (ch *channel) Call(ctx context.Context, name string, args ...interface{}) error { 40 | ch.traceSent() 41 | return ch.tx.Call(ctx, name, args...) 42 | } 43 | 44 | type rx struct { 45 | service *Service 46 | pushBuffer chan ServiceResult 47 | rxTree *streamDescription 48 | id uint64 49 | 50 | sync.Mutex 51 | queue []ServiceResult 52 | done bool 53 | } 54 | 55 | func (rx *rx) Get(ctx context.Context) (ServiceResult, error) { 56 | if rx.Closed() { 57 | return nil, ErrStreamIsClosed 58 | } 59 | 60 | var res ServiceResult 61 | 62 | // fast path 63 | select { 64 | case res = <-rx.pushBuffer: 65 | default: 66 | rx.Lock() 67 | if len(rx.queue) > 0 { 68 | res = rx.queue[0] 69 | select { 70 | case rx.pushBuffer <- res: 71 | rx.queue = rx.queue[1:] 72 | default: 73 | } 74 | } 75 | rx.Unlock() 76 | 77 | select { 78 | case res = <-rx.pushBuffer: 79 | case <-ctx.Done(): 80 | return nil, ctx.Err() 81 | } 82 | } 83 | 84 | treeMap := *(rx.rxTree) 85 | method, _, _ := res.Result() 86 | temp := treeMap[method] 87 | 88 | switch temp.Description.Type() { 89 | case emptyDispatch: 90 | rx.done = true 91 | case recursiveDispatch: 92 | // pass 93 | case otherDispatch: 94 | rx.rxTree = temp.Description 95 | } 96 | 97 | // allow to attach various protocols 98 | switch temp.Name { 99 | case "error": 100 | var ( 101 | catAndCode [2]int 102 | message string 103 | ) 104 | 105 | if err := res.ExtractTuple(&catAndCode, &message); err != nil { 106 | return res, err 107 | } 108 | 109 | res.setError(&ErrRequest{ 110 | Message: message, 111 | Category: catAndCode[0], 112 | Code: catAndCode[1], 113 | }) 114 | } 115 | 116 | return res, nil 117 | } 118 | 119 | func (rx *rx) Closed() bool { 120 | return rx.done 121 | } 122 | 123 | func (rx *rx) push(res ServiceResult) { 124 | rx.Lock() 125 | rx.queue = append(rx.queue, res) 126 | select { 127 | case rx.pushBuffer <- rx.queue[0]: 128 | rx.queue = rx.queue[1:] 129 | default: 130 | } 131 | rx.Unlock() 132 | 133 | treeMap := *(rx.rxTree) 134 | method, _, _ := res.Result() 135 | if temp := treeMap[method]; temp.Description.Type() == emptyDispatch { 136 | rx.service.sessions.Detach(rx.id) 137 | } 138 | } 139 | 140 | type tx struct { 141 | service *Service 142 | txTree *streamDescription 143 | id uint64 144 | done bool 145 | 146 | headers CocaineHeaders 147 | } 148 | 149 | func (tx *tx) Call(ctx context.Context, name string, args ...interface{}) error { 150 | if tx.done { 151 | return fmt.Errorf("tx is done") 152 | } 153 | 154 | method, err := tx.txTree.MethodByName(name) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | treeMap := *(tx.txTree) 160 | temp := treeMap[method] 161 | 162 | switch temp.Description.Type() { 163 | case emptyDispatch: 164 | tx.done = true 165 | 166 | case recursiveDispatch: 167 | //pass 168 | 169 | case otherDispatch: 170 | tx.txTree = temp.Description 171 | } 172 | 173 | msg := &Message{ 174 | CommonMessageInfo: CommonMessageInfo{tx.id, method}, 175 | Payload: args, 176 | Headers: tx.headers, 177 | } 178 | 179 | tx.service.sendMsg(msg) 180 | return nil 181 | } 182 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/codec/msgpack_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This will create golden files in a directory passed to it. 4 | # A Test calls this internally to create the golden files 5 | # So it can process them (so we don't have to checkin the files). 6 | 7 | import msgpack, msgpackrpc, sys, os, threading 8 | 9 | def get_test_data_list(): 10 | # get list with all primitive types, and a combo type 11 | l0 = [ 12 | -8, 13 | -1616, 14 | -32323232, 15 | -6464646464646464, 16 | 192, 17 | 1616, 18 | 32323232, 19 | 6464646464646464, 20 | 192, 21 | -3232.0, 22 | -6464646464.0, 23 | 3232.0, 24 | 6464646464.0, 25 | False, 26 | True, 27 | None, 28 | "someday", 29 | "", 30 | "bytestring", 31 | 1328176922000002000, 32 | -2206187877999998000, 33 | 0, 34 | -6795364578871345152 35 | ] 36 | l1 = [ 37 | { "true": True, 38 | "false": False }, 39 | { "true": "True", 40 | "false": False, 41 | "uint16(1616)": 1616 }, 42 | { "list": [1616, 32323232, True, -3232.0, {"TRUE":True, "FALSE":False}, [True, False] ], 43 | "int32":32323232, "bool": True, 44 | "LONG STRING": "123456789012345678901234567890123456789012345678901234567890", 45 | "SHORT STRING": "1234567890" }, 46 | { True: "true", 8: False, "false": 0 } 47 | ] 48 | 49 | l = [] 50 | l.extend(l0) 51 | l.append(l0) 52 | l.extend(l1) 53 | return l 54 | 55 | def build_test_data(destdir): 56 | l = get_test_data_list() 57 | for i in range(len(l)): 58 | packer = msgpack.Packer() 59 | serialized = packer.pack(l[i]) 60 | f = open(os.path.join(destdir, str(i) + '.golden'), 'wb') 61 | f.write(serialized) 62 | f.close() 63 | 64 | def doRpcServer(port, stopTimeSec): 65 | class EchoHandler(object): 66 | def Echo123(self, msg1, msg2, msg3): 67 | return ("1:%s 2:%s 3:%s" % (msg1, msg2, msg3)) 68 | def EchoStruct(self, msg): 69 | return ("%s" % msg) 70 | 71 | addr = msgpackrpc.Address('localhost', port) 72 | server = msgpackrpc.Server(EchoHandler()) 73 | server.listen(addr) 74 | # run thread to stop it after stopTimeSec seconds if > 0 75 | if stopTimeSec > 0: 76 | def myStopRpcServer(): 77 | server.stop() 78 | t = threading.Timer(stopTimeSec, myStopRpcServer) 79 | t.start() 80 | server.start() 81 | 82 | def doRpcClientToPythonSvc(port): 83 | address = msgpackrpc.Address('localhost', port) 84 | client = msgpackrpc.Client(address, unpack_encoding='utf-8') 85 | print client.call("Echo123", "A1", "B2", "C3") 86 | print client.call("EchoStruct", {"A" :"Aa", "B":"Bb", "C":"Cc"}) 87 | 88 | def doRpcClientToGoSvc(port): 89 | # print ">>>> port: ", port, " <<<<<" 90 | address = msgpackrpc.Address('localhost', port) 91 | client = msgpackrpc.Client(address, unpack_encoding='utf-8') 92 | print client.call("TestRpcInt.Echo123", ["A1", "B2", "C3"]) 93 | print client.call("TestRpcInt.EchoStruct", {"A" :"Aa", "B":"Bb", "C":"Cc"}) 94 | 95 | def doMain(args): 96 | if len(args) == 2 and args[0] == "testdata": 97 | build_test_data(args[1]) 98 | elif len(args) == 3 and args[0] == "rpc-server": 99 | doRpcServer(int(args[1]), int(args[2])) 100 | elif len(args) == 2 and args[0] == "rpc-client-python-service": 101 | doRpcClientToPythonSvc(int(args[1])) 102 | elif len(args) == 2 and args[0] == "rpc-client-go-service": 103 | doRpcClientToGoSvc(int(args[1])) 104 | else: 105 | print("Usage: msgpack_test.py " + 106 | "[testdata|rpc-server|rpc-client-python-service|rpc-client-go-service] ...") 107 | 108 | if __name__ == "__main__": 109 | doMain(sys.argv[1:]) 110 | 111 | -------------------------------------------------------------------------------- /cocaine12/proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "strings" 10 | "time" 11 | 12 | cocaine "github.com/cocaine/cocaine-framework-go/cocaine12" 13 | "github.com/ugorji/go/codec" 14 | ) 15 | 16 | var ( 17 | mhAsocket = codec.MsgpackHandle{ 18 | BasicHandle: codec.BasicHandle{ 19 | EncodeOptions: codec.EncodeOptions{ 20 | StructToArray: true, 21 | }, 22 | }, 23 | } 24 | hAsocket = &mhAsocket 25 | ) 26 | 27 | func packRequest(req *http.Request) ([]byte, error) { 28 | body, err := ioutil.ReadAll(req.Body) 29 | if err != nil { 30 | return nil, err 31 | 32 | } 33 | // method uri 1.1 headers body 34 | headers := make([][2]string, 0, len(req.Header)) 35 | for header, values := range req.Header { 36 | for _, val := range values { 37 | headers = append(headers, [2]string{header, val}) 38 | } 39 | } 40 | 41 | var task []byte 42 | codec.NewEncoderBytes(&task, hAsocket).Encode([]interface{}{ 43 | req.Method, 44 | req.URL.RequestURI(), 45 | fmt.Sprintf("%d.%d", req.ProtoMajor, req.ProtoMinor), 46 | headers, 47 | body, 48 | }) 49 | 50 | return task, nil 51 | } 52 | 53 | func process(w http.ResponseWriter, r *http.Request) { 54 | w.Header().Add("X-Powered-By", "Cocaine") 55 | defer r.Body.Close() 56 | var ( 57 | service = r.Header.Get("X-Cocaine-Service") 58 | event = r.Header.Get("X-Cocaine-Event") 59 | ) 60 | 61 | if len(service) == 0 || len(event) == 0 { 62 | var ( 63 | j = 1 64 | ) 65 | npos := strings.IndexByte(r.URL.Path[j:], '/') 66 | if npos < 0 { 67 | w.WriteHeader(http.StatusBadRequest) 68 | return 69 | } 70 | service = r.URL.Path[j : npos+j] 71 | j += npos + 1 72 | 73 | npos = strings.IndexByte(r.URL.Path[j:], '/') 74 | switch npos { 75 | case -1: 76 | event = r.URL.Path[j:] 77 | r.URL.Path = "/" 78 | case 0: 79 | // EmptyEvent 80 | w.WriteHeader(http.StatusBadRequest) 81 | return 82 | default: 83 | event = r.URL.Path[j : npos+j] 84 | r.URL.Path = r.URL.Path[j+npos:] 85 | } 86 | 87 | if len(service) == 0 || len(event) == 0 { 88 | w.WriteHeader(http.StatusBadRequest) 89 | return 90 | } 91 | } 92 | 93 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) 94 | defer cancel() 95 | app, err := cocaine.NewService(ctx, service, nil) 96 | if err != nil { 97 | w.WriteHeader(http.StatusInternalServerError) 98 | return 99 | } 100 | defer app.Close() 101 | 102 | task, err := packRequest(r) 103 | if err != nil { 104 | w.WriteHeader(http.StatusBadRequest) 105 | return 106 | } 107 | 108 | channel, err := app.Call(ctx, "enqueue", event) 109 | if err != nil { 110 | w.WriteHeader(http.StatusBadRequest) 111 | return 112 | } 113 | 114 | if err := channel.Call(ctx, "write", task); err != nil { 115 | w.WriteHeader(http.StatusInternalServerError) 116 | return 117 | } 118 | 119 | var ( 120 | body []byte 121 | startLine struct { 122 | Code int 123 | Headers [][2]string 124 | } 125 | ) 126 | 127 | packedHeaders, err := channel.Get(ctx) 128 | if err != nil { 129 | w.WriteHeader(http.StatusInternalServerError) 130 | fmt.Fprint(w, err) 131 | return 132 | } 133 | if err := packedHeaders.ExtractTuple(&body); err != nil { 134 | w.WriteHeader(http.StatusInternalServerError) 135 | fmt.Fprint(w, err) 136 | return 137 | } 138 | 139 | if err := codec.NewDecoderBytes(body, hAsocket).Decode(&startLine); err != nil { 140 | w.WriteHeader(http.StatusInternalServerError) 141 | fmt.Fprint(w, err) 142 | return 143 | } 144 | body = body[:] 145 | 146 | log.Println(startLine) 147 | for _, header := range startLine.Headers { 148 | w.Header().Add(header[0], header[1]) 149 | } 150 | w.WriteHeader(startLine.Code) 151 | 152 | BODY: 153 | for { 154 | res, err := channel.Get(ctx) 155 | switch { 156 | case err != nil: 157 | break BODY 158 | case res.Err() != nil: 159 | break BODY 160 | case channel.Closed(): 161 | break BODY 162 | default: 163 | res.ExtractTuple(&body) 164 | w.Write(body) 165 | body = body[:] 166 | } 167 | } 168 | } 169 | 170 | func NewServer() http.Handler { 171 | mux := http.NewServeMux() 172 | mux.HandleFunc("/", process) 173 | return mux 174 | } 175 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/codec/rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license found in the LICENSE file. 3 | 4 | package codec 5 | 6 | import ( 7 | "bufio" 8 | "io" 9 | "net/rpc" 10 | "sync" 11 | ) 12 | 13 | // Rpc provides a rpc Server or Client Codec for rpc communication. 14 | type Rpc interface { 15 | ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec 16 | ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec 17 | } 18 | 19 | // RpcCodecBuffered allows access to the underlying bufio.Reader/Writer 20 | // used by the rpc connection. It accomodates use-cases where the connection 21 | // should be used by rpc and non-rpc functions, e.g. streaming a file after 22 | // sending an rpc response. 23 | type RpcCodecBuffered interface { 24 | BufferedReader() *bufio.Reader 25 | BufferedWriter() *bufio.Writer 26 | } 27 | 28 | // ------------------------------------- 29 | 30 | // rpcCodec defines the struct members and common methods. 31 | type rpcCodec struct { 32 | rwc io.ReadWriteCloser 33 | dec *Decoder 34 | enc *Encoder 35 | bw *bufio.Writer 36 | br *bufio.Reader 37 | mu sync.Mutex 38 | cls bool 39 | } 40 | 41 | func newRPCCodec(conn io.ReadWriteCloser, h Handle) rpcCodec { 42 | bw := bufio.NewWriter(conn) 43 | br := bufio.NewReader(conn) 44 | return rpcCodec{ 45 | rwc: conn, 46 | bw: bw, 47 | br: br, 48 | enc: NewEncoder(bw, h), 49 | dec: NewDecoder(br, h), 50 | } 51 | } 52 | 53 | func (c *rpcCodec) BufferedReader() *bufio.Reader { 54 | return c.br 55 | } 56 | 57 | func (c *rpcCodec) BufferedWriter() *bufio.Writer { 58 | return c.bw 59 | } 60 | 61 | func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2, doFlush bool) (err error) { 62 | if c.cls { 63 | return io.EOF 64 | } 65 | if err = c.enc.Encode(obj1); err != nil { 66 | return 67 | } 68 | if writeObj2 { 69 | if err = c.enc.Encode(obj2); err != nil { 70 | return 71 | } 72 | } 73 | if doFlush && c.bw != nil { 74 | return c.bw.Flush() 75 | } 76 | return 77 | } 78 | 79 | func (c *rpcCodec) read(obj interface{}) (err error) { 80 | if c.cls { 81 | return io.EOF 82 | } 83 | //If nil is passed in, we should still attempt to read content to nowhere. 84 | if obj == nil { 85 | var obj2 interface{} 86 | return c.dec.Decode(&obj2) 87 | } 88 | return c.dec.Decode(obj) 89 | } 90 | 91 | func (c *rpcCodec) Close() error { 92 | if c.cls { 93 | return io.EOF 94 | } 95 | c.cls = true 96 | return c.rwc.Close() 97 | } 98 | 99 | func (c *rpcCodec) ReadResponseBody(body interface{}) error { 100 | return c.read(body) 101 | } 102 | 103 | // ------------------------------------- 104 | 105 | type goRpcCodec struct { 106 | rpcCodec 107 | } 108 | 109 | func (c *goRpcCodec) WriteRequest(r *rpc.Request, body interface{}) error { 110 | // Must protect for concurrent access as per API 111 | c.mu.Lock() 112 | defer c.mu.Unlock() 113 | return c.write(r, body, true, true) 114 | } 115 | 116 | func (c *goRpcCodec) WriteResponse(r *rpc.Response, body interface{}) error { 117 | c.mu.Lock() 118 | defer c.mu.Unlock() 119 | return c.write(r, body, true, true) 120 | } 121 | 122 | func (c *goRpcCodec) ReadResponseHeader(r *rpc.Response) error { 123 | return c.read(r) 124 | } 125 | 126 | func (c *goRpcCodec) ReadRequestHeader(r *rpc.Request) error { 127 | return c.read(r) 128 | } 129 | 130 | func (c *goRpcCodec) ReadRequestBody(body interface{}) error { 131 | return c.read(body) 132 | } 133 | 134 | // ------------------------------------- 135 | 136 | // goRpc is the implementation of Rpc that uses the communication protocol 137 | // as defined in net/rpc package. 138 | type goRpc struct{} 139 | 140 | // GoRpc implements Rpc using the communication protocol defined in net/rpc package. 141 | // Its methods (ServerCodec and ClientCodec) return values that implement RpcCodecBuffered. 142 | var GoRpc goRpc 143 | 144 | func (x goRpc) ServerCodec(conn io.ReadWriteCloser, h Handle) rpc.ServerCodec { 145 | return &goRpcCodec{newRPCCodec(conn, h)} 146 | } 147 | 148 | func (x goRpc) ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec { 149 | return &goRpcCodec{newRPCCodec(conn, h)} 150 | } 151 | 152 | var _ RpcCodecBuffered = (*rpcCodec)(nil) // ensure *rpcCodec implements RpcCodecBuffered 153 | -------------------------------------------------------------------------------- /cocaine/logger.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Logger struct { 10 | socketWriter 11 | verbosity int 12 | args []interface{} 13 | mutex sync.Mutex 14 | is_reconnecting bool 15 | name string 16 | } 17 | 18 | const ( 19 | LOGINGNORE = iota 20 | LOGERROR 21 | LOGWARN 22 | LOGINFO 23 | LOGDEBUG 24 | ) 25 | 26 | func NewLogger(args ...interface{}) (logger *Logger, err error) { 27 | return NewLoggerWithName("logging", args...) 28 | } 29 | 30 | func createTempService(name string, args ...interface{}) (endpoint string, verbosity int, err error) { 31 | temp, err := NewService(name, args...) 32 | if err != nil { 33 | return 34 | } 35 | defer temp.Close() 36 | 37 | res := <-temp.Call("verbosity") 38 | if res.Err() != nil { 39 | err = fmt.Errorf("%s", "cocaine: unable to receive verbosity") 40 | return 41 | } 42 | verbosity = 0 43 | if err = res.Extract(&verbosity); err != nil { 44 | return 45 | } 46 | endpoint = temp.ResolveResult.AsString() 47 | return 48 | } 49 | 50 | func createIO(name string, args ...interface{}) (sock socketWriter, verbosity int, err error) { 51 | endpoint, verbosity, err := createTempService(name, args...) 52 | if err != nil { 53 | return 54 | } 55 | sock, err = newWSocket("tcp", endpoint, time.Second*5, &LocalLoggerImpl{}) 56 | if err != nil { 57 | return 58 | } 59 | return 60 | } 61 | 62 | func NewLoggerWithName(loggerName string, args ...interface{}) (logger *Logger, err error) { 63 | sock, verbosity, err := createIO(loggerName, args...) 64 | if err != nil { 65 | return 66 | } 67 | 68 | //Create logger 69 | logger = &Logger{sock, verbosity, args, sync.Mutex{}, false, loggerName} 70 | return 71 | } 72 | 73 | func (logger *Logger) Reconnect(force bool) error { 74 | if !logger.is_reconnecting { // double check 75 | logger.mutex.Lock() 76 | defer logger.mutex.Unlock() 77 | 78 | if logger.is_reconnecting { 79 | return fmt.Errorf("%s", "Service is reconnecting now") 80 | } 81 | logger.is_reconnecting = true 82 | defer func() { logger.is_reconnecting = false }() 83 | 84 | if !force { 85 | select { 86 | case <-logger.IsClosed(): 87 | default: 88 | return fmt.Errorf("%s", "Service is already connected") 89 | } 90 | } 91 | 92 | // Create new socket 93 | sock, verbosity, err := createIO(logger.name, logger.args...) 94 | if err != nil { 95 | return err 96 | } 97 | 98 | // Dispose old IO interface 99 | logger.socketWriter.Close() 100 | // Reset IO interface 101 | logger.socketWriter = sock 102 | // Reset verbosity 103 | logger.verbosity = verbosity 104 | return nil 105 | } 106 | return fmt.Errorf("%s", "Service is reconnecting now") 107 | } 108 | 109 | func (logger *Logger) log(level int64, message ...interface{}) bool { 110 | for { 111 | select { 112 | case <-logger.IsClosed(): 113 | err := logger.Reconnect(false) 114 | if err != nil { 115 | return false 116 | } 117 | default: 118 | msg := ServiceMethod{messageInfo{0, 0}, []interface{}{level, fmt.Sprintf("app/%s", flagApp), fmt.Sprint(message...)}} 119 | logger.Write() <- packMsg(&msg) 120 | return true 121 | } 122 | } 123 | return true 124 | } 125 | 126 | func (logger *Logger) Err(message ...interface{}) { 127 | _ = LOGERROR <= logger.verbosity && logger.log(LOGERROR, message...) 128 | } 129 | 130 | func (logger *Logger) Errf(format string, args ...interface{}) { 131 | _ = LOGERROR <= logger.verbosity && logger.log(LOGERROR, fmt.Sprintf(format, args...)) 132 | } 133 | 134 | func (logger *Logger) Warn(message ...interface{}) { 135 | _ = LOGWARN <= logger.verbosity && logger.log(LOGWARN, message...) 136 | } 137 | 138 | func (logger *Logger) Warnf(format string, args ...interface{}) { 139 | _ = LOGWARN <= logger.verbosity && logger.log(LOGWARN, fmt.Sprintf(format, args...)) 140 | } 141 | 142 | func (logger *Logger) Info(message ...interface{}) { 143 | _ = LOGINFO <= logger.verbosity && logger.log(LOGINFO, message...) 144 | } 145 | 146 | func (logger *Logger) Infof(format string, args ...interface{}) { 147 | _ = LOGINFO <= logger.verbosity && logger.log(LOGINFO, fmt.Sprintf(format, args...)) 148 | } 149 | 150 | func (logger *Logger) Debug(message ...interface{}) { 151 | _ = LOGDEBUG <= logger.verbosity && logger.log(LOGDEBUG, message...) 152 | } 153 | 154 | func (logger *Logger) Debugf(format string, args ...interface{}) { 155 | _ = LOGDEBUG <= logger.verbosity && logger.log(LOGDEBUG, fmt.Sprintf(format, args...)) 156 | } 157 | 158 | func (logger *Logger) Close() { 159 | logger.socketWriter.Close() 160 | } 161 | -------------------------------------------------------------------------------- /cocaine12/service_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "sync/atomic" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestCreateIO(t *testing.T) { 15 | if _, err := serviceCreateIO(nil); err != ErrZeroEndpoints { 16 | t.Fatalf("%v is expected, but %v has been returned", ErrZeroEndpoints, err) 17 | } 18 | 19 | endpoints := []EndpointItem{ 20 | EndpointItem{"129.0.0.1", 10000}, 21 | EndpointItem{"128.0.0.1", 10000}, 22 | } 23 | _, err := serviceCreateIO(endpoints) 24 | merr, ok := err.(MultiConnectionError) 25 | if !ok { 26 | t.Fatal(err) 27 | } 28 | 29 | if merr[0].String() != endpoints[0].String() { 30 | t.Fatalf("MultiConnectionError is corrupted %v", merr) 31 | } 32 | 33 | if merr[1].String() != endpoints[1].String() { 34 | t.Fatalf("MultiConnectionError is corrupted %v", merr) 35 | } 36 | 37 | if len(merr.Error()) == 0 { 38 | t.Fatal("merr.Error() is empty") 39 | } 40 | 41 | fmt.Println(merr.Error()) 42 | } 43 | 44 | func TestService(t *testing.T) { 45 | if testing.Short() { 46 | t.Skip("skipped without Cocaine") 47 | } 48 | 49 | var ( 50 | call, write, first uint64 51 | wg sync.WaitGroup 52 | ) 53 | 54 | ctx := context.Background() 55 | s, err := NewService(ctx, "echo", nil) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | for i := 0; i < 2000; i++ { 61 | wg.Add(1) 62 | go func(i int) { 63 | defer wg.Done() 64 | 65 | ch, err := s.Call(ctx, "enqueue", "ping") 66 | if err != nil { 67 | fmt.Println(err) 68 | t.Fatal(err) 69 | } 70 | defer ch.Call(ctx, "close") 71 | atomic.AddUint64(&call, 1) 72 | 73 | ch.Call(ctx, "write", []byte("OK")) 74 | atomic.AddUint64(&write, 1) 75 | 76 | if _, err = ch.Get(ctx); err != nil { 77 | fmt.Println(err) 78 | t.Fatal(err) 79 | } 80 | atomic.AddUint64(&first, 1) 81 | }(i) 82 | 83 | } 84 | 85 | ch := make(chan struct{}) 86 | go func() { 87 | wg.Wait() 88 | close(ch) 89 | }() 90 | 91 | select { 92 | case <-ch: 93 | case <-time.After(time.Second * 10): 94 | t.Fail() 95 | panic("give me traceback") 96 | } 97 | 98 | t.Logf(` 99 | CALL %d 100 | WRITE %d, 101 | FIRST %d`, 102 | atomic.LoadUint64(&call), 103 | atomic.LoadUint64(&write), 104 | atomic.LoadUint64(&first)) 105 | } 106 | 107 | func TestDisconnectedError(t *testing.T) { 108 | if testing.Short() { 109 | t.Skip("skipped without Cocaine") 110 | } 111 | 112 | ctx := context.Background() 113 | 114 | s, err := NewService(ctx, "locator", nil) 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | 119 | // passing wrong arguments leads to disconnect 120 | ch, err := s.Call(ctx, "resolve", 1, 2, 3, 4, 5, 6) 121 | if err != nil { 122 | t.Fatal(err) 123 | } 124 | 125 | _, err = ch.Get(ctx) 126 | assert.EqualError(t, err, "Disconnected") 127 | } 128 | 129 | func TestReconnection(t *testing.T) { 130 | if testing.Short() { 131 | t.Skip("skipped without Cocaine") 132 | } 133 | 134 | ctx := context.Background() 135 | 136 | s, err := NewService(ctx, "locator", nil) 137 | if err != nil { 138 | t.Fatal(err) 139 | } 140 | 141 | // passing wrong arguments leads to disconnect 142 | ch, err := s.Call(ctx, "resolve", 1, 2, 3, 4, 5, 6) 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | _, err = ch.Get(ctx) 147 | assert.EqualError(t, err, "Disconnected") 148 | 149 | _, err = s.Call(ctx, "resolve", 1, 2, 3, 4, 5, 6) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | } 154 | 155 | func TestTimeoutError(t *testing.T) { 156 | if testing.Short() { 157 | t.Skip("skipped without Cocaine") 158 | } 159 | 160 | ctx := context.Background() 161 | s, err := NewService(ctx, "locator", nil) 162 | if err != nil { 163 | t.Fatal(err) 164 | } 165 | 166 | ctx, _ = context.WithTimeout(context.Background(), time.Microsecond*5) 167 | // passing wrong arguments leads to disconnect 168 | ch, err := s.Call(ctx, "resolve", "locator") 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | 173 | _, err = ch.Get(ctx) 174 | if !assert.Error(t, ctx.Err()) { 175 | t.FailNow() 176 | } 177 | assert.EqualError(t, err, ctx.Err().Error()) 178 | } 179 | 180 | func TestRxClosedGet(t *testing.T) { 181 | if testing.Short() { 182 | t.Skip("skipped without Cocaine") 183 | } 184 | 185 | ctx, _ := context.WithTimeout(context.Background(), time.Second*5) 186 | s, err := NewService(ctx, "locator", nil) 187 | if err != nil { 188 | t.Fatal(err) 189 | } 190 | 191 | for _, v := range s.ServiceInfo.API { 192 | t.Logf("%s %v %v\n", v.Name, v.Downstream, v.Upstream) 193 | for k, j := range *v.Upstream { 194 | t.Logf("%v %v\n", k, j) 195 | } 196 | } 197 | 198 | // passing wrong arguments leads to disconnect 199 | ch, err := s.Call(ctx, "connect", 1111) 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | 204 | _, err = ch.Get(ctx) 205 | _, err = ch.Get(ctx) 206 | assert.EqualError(t, err, ErrStreamIsClosed.Error()) 207 | } 208 | -------------------------------------------------------------------------------- /cocaine12/trace.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | const ( 12 | TraceInfoValue = "trace.traceinfo" 13 | TraceStartTimeValue = "trace.starttime" 14 | ) 15 | 16 | var ( 17 | initTraceLogger sync.Once 18 | traceLogger Logger 19 | 20 | closeDummySpan CloseSpan = func() {} 21 | ) 22 | 23 | func GetTraceInfo(ctx context.Context) *TraceInfo { 24 | if val, ok := ctx.Value(TraceInfoValue).(TraceInfo); ok { 25 | return &val 26 | } 27 | return nil 28 | } 29 | 30 | // CloseSpan closes attached span. It should be call after 31 | // the rpc ends. 32 | type CloseSpan func() 33 | 34 | type TraceInfo struct { 35 | Trace, Span, Parent uint64 36 | logger Logger 37 | } 38 | 39 | func (traceInfo *TraceInfo) getLog() Logger { 40 | if traceInfo.logger != nil { 41 | return traceInfo.logger 42 | } 43 | 44 | initTraceLogger.Do(func() { 45 | var err error 46 | traceLogger, err = NewLogger(context.Background()) 47 | // there must be no error 48 | if err != nil { 49 | panic(fmt.Sprintf("unable to create trace logger: %v", err)) 50 | } 51 | }) 52 | return traceLogger 53 | } 54 | 55 | type traced struct { 56 | context.Context 57 | traceInfo TraceInfo 58 | startTime time.Time 59 | } 60 | 61 | func (t *traced) Value(key interface{}) interface{} { 62 | switch key { 63 | case TraceInfoValue: 64 | return t.traceInfo 65 | case TraceStartTimeValue: 66 | return t.startTime 67 | default: 68 | return t.Context.Value(key) 69 | } 70 | } 71 | 72 | // It might be used in client applications. 73 | func BeginNewTraceContext(ctx context.Context) context.Context { 74 | return BeginNewTraceContextWithLogger(ctx, nil) 75 | } 76 | 77 | func BeginNewTraceContextWithLogger(ctx context.Context, logger Logger) context.Context { 78 | ts := uint64(rand.Int63()) 79 | return AttachTraceInfo(ctx, TraceInfo{ 80 | Trace: ts, 81 | Span: ts, 82 | Parent: 0, 83 | logger: logger, 84 | }) 85 | } 86 | 87 | // AttachTraceInfo binds given TraceInfo to the context. 88 | // If ctx is nil, then TraceInfo will be attached to context.Background() 89 | func AttachTraceInfo(ctx context.Context, traceInfo TraceInfo) context.Context { 90 | if ctx == nil { 91 | ctx = context.Background() 92 | } 93 | 94 | return &traced{ 95 | Context: ctx, 96 | traceInfo: traceInfo, 97 | startTime: time.Now(), 98 | } 99 | } 100 | 101 | // CleanTraceInfo might be used to clear context instance from trace info 102 | // to disable tracing in some RPC calls to get rid of overhead 103 | func CleanTraceInfo(ctx context.Context) context.Context { 104 | return context.WithValue(ctx, TraceInfoValue, nil) 105 | } 106 | 107 | // NewSpan starts new span and returns a context with attached TraceInfo and Done. 108 | // If ctx is nil or has no TraceInfo new span won't start to support sampling, 109 | // so it's user responsibility to make sure that the context has TraceInfo. 110 | // Anyway it safe to call CloseSpan function even in this case, it actually does nothing. 111 | func NewSpan(ctx context.Context, rpcNameFormat string, args ...interface{}) (context.Context, CloseSpan) { 112 | if ctx == nil { 113 | // I'm not sure it is a valid action. 114 | // According to the rule "no trace info, no new span" 115 | // to support sampling, nil Context has no TraceInfo, so 116 | // it cannot start new Span. 117 | return context.Background(), closeDummySpan 118 | } 119 | 120 | traceInfo := GetTraceInfo(ctx) 121 | if traceInfo == nil { 122 | // given context has no TraceInfo 123 | // so we can't start new trace to support sampling. 124 | // closeDummySpan does nohing 125 | return ctx, closeDummySpan 126 | } 127 | 128 | var rpcName string 129 | if len(args) > 0 { 130 | rpcName = fmt.Sprintf(rpcNameFormat, args...) 131 | } else { 132 | rpcName = rpcNameFormat 133 | } 134 | 135 | // startTime is not used only to log the start of an RPC 136 | // It's stored in Context to calculate the RPC call duration. 137 | // A user can get it via Context.Value(TraceStartTimeValue) 138 | startTime := time.Now() 139 | 140 | // Tracing magic: 141 | // * the previous span becomes our parent 142 | // * new span is set as random number 143 | // * trace still stays the same 144 | traceInfo.Parent = traceInfo.Span 145 | traceInfo.Span = uint64(rand.Int63()) 146 | 147 | traceInfo.getLog().WithFields(Fields{ 148 | "trace_id": fmt.Sprintf("%x", traceInfo.Trace), 149 | "span_id": fmt.Sprintf("%x", traceInfo.Span), 150 | "parent_id": fmt.Sprintf("%x", traceInfo.Parent), 151 | "real_timestamp": startTime.UnixNano() / 1000, 152 | "rpc_name": rpcName, 153 | }).Infof("start") 154 | 155 | ctx = &traced{ 156 | Context: ctx, 157 | traceInfo: *traceInfo, 158 | startTime: startTime, 159 | } 160 | 161 | return ctx, func() { 162 | now := time.Now() 163 | duration := now.Sub(startTime) 164 | traceInfo.getLog().WithFields(Fields{ 165 | "trace_id": fmt.Sprintf("%x", traceInfo.Trace), 166 | "span_id": fmt.Sprintf("%x", traceInfo.Span), 167 | "parent_id": fmt.Sprintf("%x", traceInfo.Parent), 168 | "real_timestamp": now.UnixNano() / 1000, 169 | "duration": duration.Nanoseconds() / 1000, 170 | "rpc_name": rpcName, 171 | }).Infof("finish") 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /cocaine12/protocol.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "reflect" 9 | ) 10 | 11 | const ( 12 | traceId = 80 13 | spanId = 81 14 | parentId = 82 15 | ) 16 | 17 | var ( 18 | traceIdName = []byte("trace_id") 19 | spanIdName = []byte("span_id") 20 | parentIdName = []byte("parent_id") 21 | 22 | traceValueMap = map[uint64]struct{}{ 23 | traceId: struct{}{}, 24 | spanId: struct{}{}, 25 | parentId: struct{}{}, 26 | } 27 | ) 28 | 29 | var ( 30 | ErrInvalidHeaderLength = errors.New("invalid header size") 31 | ErrInvalidHeaderType = errors.New("invalid header type") 32 | ErrInvalidTraceType = errors.New("invalid trace header number type") 33 | ErrInvalidTraceNumber = errors.New("invalid trace header number") 34 | ErrInvalidTraceValueType = errors.New("invalid trace value type") 35 | 36 | ErrNotAllTracesPresent = errors.New("not all trace values present") 37 | ) 38 | 39 | // CommonMessageInfo consists of a session number and a message type 40 | type CommonMessageInfo struct { 41 | // Session id 42 | Session uint64 43 | // Message type number 44 | MsgType uint64 45 | } 46 | 47 | func getTrace(header interface{}) (uint64, []byte, error) { 48 | switch t := header.(type) { 49 | case uint: 50 | return uint64(t), nil, nil 51 | case uint32: 52 | return uint64(t), nil, nil 53 | case uint64: 54 | return t, nil, nil 55 | case int: 56 | return uint64(t), nil, nil 57 | case int32: 58 | return uint64(t), nil, nil 59 | case int64: 60 | return uint64(t), nil, nil 61 | 62 | case []interface{}: 63 | if len(t) != 3 { 64 | return 0, nil, ErrInvalidHeaderLength 65 | } 66 | 67 | var ( 68 | traceNum uint64 69 | traceVal []byte 70 | ) 71 | 72 | switch num := t[1].(type) { 73 | case uint: 74 | traceNum = uint64(num) 75 | case uint32: 76 | traceNum = uint64(num) 77 | case uint64: 78 | traceNum = num 79 | case int: 80 | traceNum = uint64(num) 81 | case int32: 82 | traceNum = uint64(num) 83 | case int64: 84 | traceNum = uint64(num) 85 | case []byte: 86 | decodedTraceNum, err := decodeRawTraceName(num) 87 | if err != nil { 88 | return 0, nil, err 89 | } 90 | traceNum = decodedTraceNum 91 | default: 92 | fmt.Println(reflect.TypeOf(t[1])) 93 | return 0, nil, ErrInvalidTraceType 94 | } 95 | 96 | if _, ok := traceValueMap[traceNum]; !ok { 97 | return 0, nil, ErrInvalidTraceNumber 98 | } 99 | 100 | switch val := t[2].(type) { 101 | case []byte: 102 | traceVal = val 103 | case string: 104 | traceVal = []byte(val) 105 | default: 106 | return 0, nil, ErrInvalidTraceValueType 107 | } 108 | 109 | return traceNum, traceVal, nil 110 | default: 111 | fmt.Printf("%v\n", reflect.TypeOf(t)) 112 | } 113 | 114 | return 0, nil, ErrInvalidHeaderType 115 | } 116 | 117 | func decodeRawTraceName(name []byte) (uint64, error) { 118 | if bytes.Equal(name, traceIdName) { 119 | return traceId, nil 120 | } else if bytes.Equal(name, spanIdName) { 121 | return spanId, nil 122 | } else if bytes.Equal(name, parentIdName) { 123 | return parentId, nil 124 | } 125 | return 0, ErrInvalidTraceType 126 | } 127 | 128 | type CocaineHeaders []interface{} 129 | 130 | func (h CocaineHeaders) getTraceData() (traceInfo TraceInfo, err error) { 131 | var i = 0 132 | for _, header := range h { 133 | number, buffer, zerr := getTrace(header) 134 | if zerr != nil { 135 | continue 136 | } 137 | switch number { 138 | case traceId: 139 | if traceInfo.Trace, err = decodeTracingId(buffer); err != nil { 140 | return 141 | } 142 | 143 | case spanId: 144 | if traceInfo.Span, err = decodeTracingId(buffer); err != nil { 145 | return 146 | } 147 | 148 | case parentId: 149 | if buffer == nil { 150 | traceInfo.Parent = 0 151 | } else { 152 | if traceInfo.Parent, err = decodeTracingId(buffer); err != nil { 153 | return 154 | } 155 | } 156 | 157 | default: 158 | continue 159 | } 160 | 161 | i++ 162 | if i == 3 { 163 | return 164 | } 165 | } 166 | 167 | return traceInfo, ErrNotAllTracesPresent 168 | } 169 | 170 | func decodeTracingId(b []byte) (uint64, error) { 171 | var tracingId uint64 172 | err := binary.Read(bytes.NewReader(b), binary.LittleEndian, &tracingId) 173 | return tracingId, err 174 | } 175 | 176 | func traceInfoToHeaders(info *TraceInfo) (CocaineHeaders, error) { 177 | var ( 178 | offset = 0 179 | buff = new(bytes.Buffer) 180 | headers = make(CocaineHeaders, 0, 3) 181 | ) 182 | 183 | if err := binary.Write(buff, binary.LittleEndian, info.Trace); err != nil { 184 | return headers, err 185 | } 186 | headers = append(headers, []interface{}{false, traceId, buff.Bytes()[offset:]}) 187 | offset = buff.Len() 188 | 189 | if err := binary.Write(buff, binary.LittleEndian, info.Span); err != nil { 190 | return headers, err 191 | } 192 | headers = append(headers, []interface{}{false, spanId, buff.Bytes()[offset:]}) 193 | offset = buff.Len() 194 | 195 | if err := binary.Write(buff, binary.LittleEndian, info.Parent); err != nil { 196 | return headers, err 197 | } 198 | headers = append(headers, []interface{}{false, parentId, buff.Bytes()[offset:]}) 199 | offset = buff.Len() 200 | 201 | return headers, nil 202 | } 203 | 204 | type Message struct { 205 | // _struct bool `codec:",toarray"` 206 | CommonMessageInfo 207 | Payload []interface{} 208 | Headers CocaineHeaders 209 | } 210 | 211 | func (m *Message) String() string { 212 | return fmt.Sprintf("message %v %v payload %v", m.MsgType, m.Session, m.Payload) 213 | } 214 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/codec/0doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license found in the LICENSE file. 3 | 4 | /* 5 | High Performance, Feature-Rich Idiomatic Go encoding library for msgpack and binc . 6 | 7 | Supported Serialization formats are: 8 | 9 | - msgpack: [https://github.com/msgpack/msgpack] 10 | - binc: [http://github.com/ugorji/binc] 11 | 12 | To install: 13 | 14 | go get github.com/ugorji/go/codec 15 | 16 | The idiomatic Go support is as seen in other encoding packages in 17 | the standard library (ie json, xml, gob, etc). 18 | 19 | Rich Feature Set includes: 20 | 21 | - Simple but extremely powerful and feature-rich API 22 | - Very High Performance. 23 | Our extensive benchmarks show us outperforming Gob, Json and Bson by 2-4X. 24 | This was achieved by taking extreme care on: 25 | - managing allocation 26 | - function frame size (important due to Go's use of split stacks), 27 | - reflection use (and by-passing reflection for common types) 28 | - recursion implications 29 | - zero-copy mode (encoding/decoding to byte slice without using temp buffers) 30 | - Correct. 31 | Care was taken to precisely handle corner cases like: 32 | overflows, nil maps and slices, nil value in stream, etc. 33 | - Efficient zero-copying into temporary byte buffers 34 | when encoding into or decoding from a byte slice. 35 | - Standard field renaming via tags 36 | - Encoding from any value 37 | (struct, slice, map, primitives, pointers, interface{}, etc) 38 | - Decoding into pointer to any non-nil typed value 39 | (struct, slice, map, int, float32, bool, string, reflect.Value, etc) 40 | - Supports extension functions to handle the encode/decode of custom types 41 | - Support Go 1.2 encoding.BinaryMarshaler/BinaryUnmarshaler 42 | - Schema-less decoding 43 | (decode into a pointer to a nil interface{} as opposed to a typed non-nil value). 44 | Includes Options to configure what specific map or slice type to use 45 | when decoding an encoded list or map into a nil interface{} 46 | - Provides a RPC Server and Client Codec for net/rpc communication protocol. 47 | - Msgpack Specific: 48 | - Provides extension functions to handle spec-defined extensions (binary, timestamp) 49 | - Options to resolve ambiguities in handling raw bytes (as string or []byte) 50 | during schema-less decoding (decoding into a nil interface{}) 51 | - RPC Server/Client Codec for msgpack-rpc protocol defined at: 52 | https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md 53 | - Fast Paths for some container types: 54 | For some container types, we circumvent reflection and its associated overhead 55 | and allocation costs, and encode/decode directly. These types are: 56 | Slice of all builtin types and interface{}, 57 | map of all builtin types and interface{} to string, interface{}, int, int64, uint64 58 | symetrical maps of all builtin types and interface{} 59 | 60 | Extension Support 61 | 62 | Users can register a function to handle the encoding or decoding of 63 | their custom types. 64 | 65 | There are no restrictions on what the custom type can be. Some examples: 66 | 67 | type BisSet []int 68 | type BitSet64 uint64 69 | type UUID string 70 | type MyStructWithUnexportedFields struct { a int; b bool; c []int; } 71 | type GifImage struct { ... } 72 | 73 | As an illustration, MyStructWithUnexportedFields would normally be 74 | encoded as an empty map because it has no exported fields, while UUID 75 | would be encoded as a string. However, with extension support, you can 76 | encode any of these however you like. 77 | 78 | RPC 79 | 80 | RPC Client and Server Codecs are implemented, so the codecs can be used 81 | with the standard net/rpc package. 82 | 83 | Usage 84 | 85 | Typical usage model: 86 | 87 | // create and configure Handle 88 | var ( 89 | bh codec.BincHandle 90 | mh codec.MsgpackHandle 91 | ) 92 | 93 | mh.MapType = reflect.TypeOf(map[string]interface{}(nil)) 94 | 95 | // configure extensions 96 | // e.g. for msgpack, define functions and enable Time support for tag 1 97 | // mh.AddExt(reflect.TypeOf(time.Time{}), 1, myMsgpackTimeEncodeExtFn, myMsgpackTimeDecodeExtFn) 98 | 99 | // create and use decoder/encoder 100 | var ( 101 | r io.Reader 102 | w io.Writer 103 | b []byte 104 | h = &bh // or mh to use msgpack 105 | ) 106 | 107 | dec = codec.NewDecoder(r, h) 108 | dec = codec.NewDecoderBytes(b, h) 109 | err = dec.Decode(&v) 110 | 111 | enc = codec.NewEncoder(w, h) 112 | enc = codec.NewEncoderBytes(&b, h) 113 | err = enc.Encode(v) 114 | 115 | //RPC Server 116 | go func() { 117 | for { 118 | conn, err := listener.Accept() 119 | rpcCodec := codec.GoRpc.ServerCodec(conn, h) 120 | //OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h) 121 | rpc.ServeCodec(rpcCodec) 122 | } 123 | }() 124 | 125 | //RPC Communication (client side) 126 | conn, err = net.Dial("tcp", "localhost:5555") 127 | rpcCodec := codec.GoRpc.ClientCodec(conn, h) 128 | //OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h) 129 | client := rpc.NewClientWithCodec(rpcCodec) 130 | 131 | Representative Benchmark Results 132 | 133 | Run the benchmark suite using: 134 | go test -bi -bench=. -benchmem 135 | 136 | To run full benchmark suite (including against vmsgpack and bson), 137 | see notes in ext_dep_test.go 138 | 139 | */ 140 | package codec 141 | -------------------------------------------------------------------------------- /cocaine/httpreq.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "reflect" 10 | 11 | "github.com/ugorji/go/codec" 12 | ) 13 | 14 | type Headers [][2]string 15 | 16 | type HTTPReq struct { 17 | *http.Request 18 | } 19 | 20 | // TBD: Extract more info 21 | func UnpackProxyRequest(raw []byte) (*http.Request, error) { 22 | var ( 23 | mh codec.MsgpackHandle 24 | h = &mh 25 | ) 26 | var v []interface{} 27 | mh.SliceType = reflect.TypeOf(Headers(nil)) 28 | codec.NewDecoderBytes(raw, h).Decode(&v) 29 | r, err := http.NewRequest(string(v[0].([]uint8)), string(v[1].([]uint8)), bytes.NewBuffer(v[4].([]byte))) 30 | if err != nil { 31 | return nil, err 32 | } 33 | r.Header = CocaineHeaderToHttpHeader(v[3].(Headers)) 34 | 35 | r.Host = r.Header.Get("Host") 36 | 37 | if xRealIp := r.Header.Get("X-Real-IP"); xRealIp != "" { 38 | r.RemoteAddr = xRealIp 39 | } 40 | 41 | err = decompressBody(r) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return r, nil 47 | } 48 | 49 | func WriteHead(code int, headers Headers) interface{} { 50 | return []interface{}{code, headers} 51 | } 52 | 53 | func HttpHeaderToCocaineHeader(header http.Header) Headers { 54 | hdr := Headers{} 55 | for headerName, headerValues := range header { 56 | for _, headerValue := range headerValues { 57 | hdr = append(hdr, [2]string{headerName, headerValue}) 58 | } 59 | } 60 | return hdr 61 | } 62 | 63 | func CocaineHeaderToHttpHeader(hdr Headers) http.Header { 64 | header := http.Header{} 65 | for _, hdrValues := range hdr { 66 | header.Add(hdrValues[0], hdrValues[1]) 67 | } 68 | return header 69 | } 70 | 71 | // WrapHandler provides opportunity for using Go web frameworks, which supports http.Handler interface 72 | // 73 | // Trivial example which is used martini web framework 74 | // 75 | // import ( 76 | // "github.com/cocaine/cocaine-framework-go/cocaine" 77 | // "github.com/codegangsta/martini" 78 | // ) 79 | // 80 | // 81 | // func main() { 82 | // m := martini.Classic() 83 | // m.Get("", func() string { 84 | // return "This is an example server" 85 | // }) 86 | // 87 | // m.Get("/hw", func() string { 88 | // return "Hello world!" 89 | // }) 90 | // 91 | // binds := map[string]cocaine.EventHandler{ 92 | // "example": cocaine.WrapHandler(m, nil), 93 | // } 94 | // if worker, err := cocaine.NewWorker(); err == nil{ 95 | // worker.Loop(binds) 96 | // }else{ 97 | // panic(err) 98 | // } 99 | // } 100 | // 101 | func WrapHandler(handler http.Handler, logger *Logger) EventHandler { 102 | var err error 103 | if logger == nil { 104 | logger, err = NewLogger() 105 | if err != nil { 106 | panic(fmt.Sprintf("Could not initialize logger due to error: %v", err)) 107 | } 108 | } 109 | var wrapper = func(request *Request, response *Response) { 110 | if httpRequest, err := UnpackProxyRequest(<-request.Read()); err != nil { 111 | logger.Errf("Could not unpack http request due to error %v", err) 112 | response.Write(WriteHead(400, Headers{})) 113 | } else { 114 | w := &ResponseWriter{ 115 | cRes: response, 116 | req: httpRequest, 117 | handlerHeader: make(http.Header), 118 | contentLength: -1, 119 | wroteHeader: false, 120 | logger: logger, 121 | } 122 | handler.ServeHTTP(w, httpRequest) 123 | w.finishRequest() 124 | } 125 | response.Close() 126 | } 127 | 128 | return wrapper 129 | } 130 | 131 | // WrapHandlerFunc provides opportunity for using Go web frameworks, which supports http.HandlerFunc interface 132 | // 133 | // Trivial example is 134 | // 135 | // import ( 136 | // "net/http" 137 | // "github.com/cocaine/cocaine-framework-go/cocaine" 138 | // ) 139 | // 140 | // func handler(w http.ResponseWriter, req *http.Request) { 141 | // w.Header().Set("Content-Type", "text/plain") 142 | // w.Write([]byte("This is an example server.\n")) 143 | // } 144 | // 145 | // func main() { 146 | // binds := map[string]cocaine.EventHandler{ 147 | // "example": cocaine.WrapHandlerFunc(handler, nil), 148 | // } 149 | // if worker, err := cocaine.NewWorker(); err == nil{ 150 | // worker.Loop(binds) 151 | // }else{ 152 | // panic(err) 153 | // } 154 | // } 155 | // 156 | func WrapHandlerFunc(hf http.HandlerFunc, logger *Logger) EventHandler { 157 | return WrapHandler(http.HandlerFunc(hf), logger) 158 | } 159 | 160 | func WrapHandleFuncs(hfs map[string]http.HandlerFunc, logger *Logger) (handlers map[string]EventHandler) { 161 | handlers = map[string]EventHandler{} 162 | for key, hf := range hfs { 163 | handlers[key] = WrapHandlerFunc(hf, logger) 164 | } 165 | return 166 | } 167 | 168 | // By default decompressors such as gzip do not close underlying stream 169 | // so we need to close it manually. This decorator makes it 170 | type decompressReaderCloser struct { 171 | decompressReader io.Closer 172 | body io.Closer 173 | } 174 | 175 | func (this decompressReaderCloser) Close() error { 176 | errDec := this.decompressReader.Close() 177 | errBody := this.body.Close() 178 | 179 | if errDec != nil { 180 | return errDec 181 | } else { 182 | return errBody 183 | } 184 | } 185 | 186 | type readerAndCloser struct { 187 | io.Reader 188 | io.Closer 189 | } 190 | 191 | // If body is compressed it will be decompressed 192 | // Currently only gzip supported 193 | func decompressBody(req *http.Request) error { 194 | if req.ContentLength > 0 && req.Header.Get("Content-Encoding") == "gzip" { 195 | gzReader, err := gzip.NewReader(req.Body) 196 | if err != nil { 197 | req.Body.Close() 198 | return fmt.Errorf("Could not decompress gzip body due to error %v", err) 199 | } 200 | 201 | req.Header.Del("Content-Encoding") 202 | req.Header.Del("Content-Length") 203 | req.ContentLength = -1 204 | req.Body = readerAndCloser{gzReader, decompressReaderCloser{gzReader, req.Body}} 205 | } 206 | 207 | return nil 208 | } 209 | -------------------------------------------------------------------------------- /cocaine12/handler.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "syscall" 8 | ) 9 | 10 | type request struct { 11 | messageTypeDetector 12 | fromWorker chan *Message 13 | toHandler chan *Message 14 | closed chan struct{} 15 | } 16 | 17 | const ( 18 | cworkererrorcategory = 42 19 | cdefaulterrrorcode = 100 20 | ) 21 | 22 | var ( 23 | // ErrStreamIsClosed means that a response stream is closed 24 | ErrStreamIsClosed = errors.New("Stream is closed") 25 | // ErrBadPayload means that a message payload is malformed 26 | ErrBadPayload = errors.New("payload is not []byte") 27 | // ErrMalformedErrorMessage means that we receive a corrupted or 28 | // unproper message 29 | ErrMalformedErrorMessage = &ErrRequest{ 30 | Message: "malformed error message", 31 | Category: cworkererrorcategory, 32 | Code: cdefaulterrrorcode, 33 | } 34 | ) 35 | 36 | func newRequest(mtd messageTypeDetector) *request { 37 | request := &request{ 38 | messageTypeDetector: mtd, 39 | fromWorker: make(chan *Message), 40 | toHandler: make(chan *Message), 41 | closed: make(chan struct{}), 42 | } 43 | 44 | go loop( 45 | // input 46 | request.fromWorker, 47 | // output 48 | request.toHandler, 49 | // onclose 50 | request.closed, 51 | ) 52 | 53 | return request 54 | } 55 | 56 | func (request *request) Read(ctx context.Context) ([]byte, error) { 57 | select { 58 | // Choke never reaches this select, 59 | // as it is simulated by closing toHandler channel. 60 | // So msg can be either Chunk or Error. 61 | case msg, ok := <-request.toHandler: 62 | if !ok { 63 | return nil, ErrStreamIsClosed 64 | } 65 | 66 | if request.isChunk(msg) { 67 | if result, isByte := msg.Payload[0].([]byte); isByte { 68 | return result, nil 69 | } 70 | return nil, ErrBadPayload 71 | } 72 | 73 | // Error message 74 | if len(msg.Payload) == 0 { 75 | return nil, ErrMalformedErrorMessage 76 | } 77 | 78 | var perr struct { 79 | CodeInfo [2]int 80 | Message string 81 | } 82 | 83 | if err := convertPayload(msg.Payload, &perr); err != nil { 84 | return nil, err 85 | } 86 | 87 | return nil, &ErrRequest{ 88 | Message: perr.Message, 89 | Category: perr.CodeInfo[0], 90 | Code: perr.CodeInfo[1], 91 | } 92 | case <-ctx.Done(): 93 | return nil, ctx.Err() 94 | } 95 | } 96 | 97 | func (request *request) push(msg *Message) { 98 | request.fromWorker <- msg 99 | } 100 | 101 | func (request *request) Close() { 102 | close(request.closed) 103 | } 104 | 105 | type response struct { 106 | handlerProtocolGenerator 107 | session uint64 108 | toWorker asyncSender 109 | closed bool 110 | } 111 | 112 | func newResponse(h handlerProtocolGenerator, session uint64, toWorker asyncSender) *response { 113 | response := &response{ 114 | handlerProtocolGenerator: h, 115 | session: session, 116 | toWorker: toWorker, 117 | closed: false, 118 | } 119 | 120 | return response 121 | } 122 | 123 | // Write sends chunk of data to a client. 124 | // It copies data to follow io.Writer rule about not retaining a buffer 125 | func (r *response) Write(data []byte) (n int, err error) { 126 | // According to io.Writer spec 127 | // I must not retain provided []byte 128 | var cpy = append([]byte(nil), data...) 129 | if err := r.ZeroCopyWrite(cpy); err != nil { 130 | return 0, err 131 | } 132 | 133 | return len(data), nil 134 | } 135 | 136 | // ZeroCopyWrite sends data to a client. 137 | // Response takes the ownership of the buffer, so provided buffer must not be edited. 138 | func (r *response) ZeroCopyWrite(data []byte) error { 139 | if r.isClosed() { 140 | return io.ErrClosedPipe 141 | } 142 | 143 | r.toWorker.Send(r.newChunk(r.session, data)) 144 | return nil 145 | } 146 | 147 | // Notify a client about finishing the datastream. 148 | func (r *response) Close() error { 149 | if r.isClosed() { 150 | // we treat it as a network connection 151 | return syscall.EINVAL 152 | } 153 | 154 | r.close() 155 | r.toWorker.Send(r.newChoke(r.session)) 156 | return nil 157 | } 158 | 159 | // Send error to a client. Specify code and message, which describes this error. 160 | func (r *response) ErrorMsg(code int, message string) error { 161 | if r.isClosed() { 162 | return io.ErrClosedPipe 163 | } 164 | 165 | r.close() 166 | r.toWorker.Send(r.newError( 167 | // current session number 168 | r.session, 169 | // category 170 | cworkererrorcategory, 171 | // error code 172 | code, 173 | // error message 174 | message, 175 | )) 176 | return nil 177 | } 178 | 179 | func (r *response) close() { 180 | r.closed = true 181 | } 182 | 183 | func (r *response) isClosed() bool { 184 | return r.closed 185 | } 186 | 187 | func loop(input <-chan *Message, output chan *Message, onclose <-chan struct{}) { 188 | defer close(output) 189 | 190 | var ( 191 | pending []*Message 192 | closed = onclose 193 | ) 194 | 195 | for { 196 | var ( 197 | out chan *Message 198 | first *Message 199 | ) 200 | 201 | if len(pending) > 0 { 202 | // if we have data to send, 203 | // pick the first element from the queue 204 | // and unlock `out case` in select 205 | // Othrewise `out` is nil 206 | first = pending[0] 207 | out = output 208 | } else if closed == nil { 209 | // Pending queue is empty 210 | // and there will be no incoming data 211 | // as request is closed 212 | return 213 | } 214 | 215 | select { 216 | case incoming := <-input: 217 | pending = append(pending, incoming) 218 | 219 | case out <- first: 220 | // help GC a bit 221 | pending[0] = nil 222 | // it should be done 223 | // without memory copy/allocate 224 | pending = pending[1:] 225 | 226 | case <-closed: 227 | // It will be triggered on 228 | // the next iteration as closed is closed 229 | closed = nil 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /cocaine/message.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | 8 | uuid "github.com/satori/go.uuid" 9 | "github.com/ugorji/go/codec" 10 | ) 11 | 12 | const ( 13 | HANDSHAKE = iota 14 | HEARTBEAT 15 | TERMINATE 16 | INVOKE 17 | CHUNK 18 | ERROR 19 | CHOKE 20 | ) 21 | 22 | type rawMessage []byte 23 | 24 | var ( 25 | mh codec.MsgpackHandle 26 | h = &mh 27 | ) 28 | 29 | type messageInfo struct { 30 | Number int64 31 | Channel int64 32 | } 33 | 34 | type handshakeStruct struct { 35 | messageInfo 36 | Uuid uuid.UUID 37 | } 38 | 39 | type heartbeat struct { 40 | messageInfo 41 | } 42 | 43 | type terminateStruct struct { 44 | messageInfo 45 | Reason string 46 | Message string 47 | } 48 | 49 | type invoke struct { 50 | messageInfo 51 | Event string 52 | } 53 | 54 | type chunk struct { 55 | messageInfo 56 | Data []byte 57 | } 58 | 59 | type errorMsg struct { 60 | messageInfo 61 | Code int 62 | Message string 63 | } 64 | 65 | type choke struct { 66 | messageInfo 67 | } 68 | 69 | type ServiceMethod struct { 70 | messageInfo 71 | Data []interface{} 72 | } 73 | 74 | type messageInterface interface { 75 | getTypeID() int64 76 | getSessionID() int64 77 | getPayload() []interface{} 78 | } 79 | 80 | func (msg *messageInfo) getTypeID() int64 { 81 | return msg.Number 82 | } 83 | 84 | func (msg *messageInfo) getSessionID() int64 { 85 | return msg.Channel 86 | } 87 | 88 | func packMsg(msg messageInterface) rawMessage { 89 | var buf []byte 90 | err := codec.NewEncoderBytes(&buf, h).Encode([]interface{}{msg.getTypeID(), msg.getSessionID(), msg.getPayload()}) 91 | if err != nil { 92 | fmt.Println(err) 93 | } 94 | return buf 95 | } 96 | 97 | //ServiceMethod 98 | func (msg *ServiceMethod) getPayload() []interface{} { 99 | return msg.Data 100 | } 101 | 102 | // handshakeStruct 103 | func unpackHandshake(session int64, data []interface{}) (msg messageInterface, err error) { 104 | u := uuid.UUID{} 105 | if uuid_t, ok := data[0].([]byte); ok { 106 | u, err = uuid.FromString(string(uuid_t)) 107 | if err != nil { 108 | return 109 | } 110 | } 111 | msg = &handshakeStruct{messageInfo{HANDSHAKE, session}, u} 112 | return 113 | } 114 | 115 | func (msg *handshakeStruct) getPayload() []interface{} { 116 | return []interface{}{msg.Uuid.String()} 117 | } 118 | 119 | // heartbeat 120 | func unpackHeartbeat(session int64, data []interface{}) (msg messageInterface, err error) { 121 | msg = &heartbeat{messageInfo{HEARTBEAT, session}} 122 | return 123 | } 124 | 125 | func (msg *heartbeat) getPayload() []interface{} { 126 | return []interface{}{} 127 | } 128 | 129 | // terminateStruct 130 | func unpackTerminate(session int64, data []interface{}) (msg messageInterface, err error) { 131 | var reason, message string 132 | if reason_t, ok := data[0].([]byte); ok { 133 | reason = string(reason_t) 134 | } 135 | if message_t, ok := data[1].([]byte); ok { 136 | message = string(message_t) 137 | } 138 | msg = &terminateStruct{messageInfo{TERMINATE, session}, reason, message} 139 | return 140 | } 141 | 142 | func (msg *terminateStruct) getPayload() []interface{} { 143 | return []interface{}{msg.Reason, msg.Message} 144 | } 145 | 146 | // invoke 147 | func unpackInvoke(session int64, data []interface{}) (msg messageInterface, err error) { 148 | var event string 149 | if event_t, ok := data[0].([]byte); ok { 150 | event = string(event_t) 151 | } else { 152 | fmt.Println("Errror") 153 | } 154 | msg = &invoke{messageInfo{INVOKE, session}, event} 155 | return 156 | } 157 | 158 | func (msg *invoke) getPayload() []interface{} { 159 | return []interface{}{msg.Event} 160 | } 161 | 162 | // chunk 163 | func unpackChunk(session int64, data []interface{}) (msg messageInterface, err error) { 164 | msgData := data[0].([]byte) 165 | msg = &chunk{messageInfo{CHUNK, session}, msgData} 166 | return 167 | } 168 | 169 | func (msg *chunk) getPayload() []interface{} { 170 | return []interface{}{msg.Data} 171 | } 172 | 173 | // Error 174 | func unpackErrorMsg(session int64, data []interface{}) (msg messageInterface, err error) { 175 | var code int 176 | var message string 177 | 178 | switch code_t := data[0].(type) { 179 | case uint64: 180 | code = int(code_t) 181 | case int64: 182 | code = int(code_t) 183 | } 184 | 185 | switch message_t := data[1].(type) { 186 | case []byte: 187 | message = string(message_t) 188 | case string: 189 | message = message_t 190 | } 191 | 192 | msg = &errorMsg{messageInfo{ERROR, session}, code, message} 193 | return 194 | } 195 | 196 | func (msg *errorMsg) getPayload() []interface{} { 197 | return []interface{}{msg.Code, msg.Message} 198 | } 199 | 200 | // choke 201 | func unpackChoke(session int64, data []interface{}) (msg messageInterface, err error) { 202 | msg = &choke{messageInfo{CHOKE, session}} 203 | return 204 | } 205 | 206 | func (msg *choke) getPayload() []interface{} { 207 | return []interface{}{} 208 | } 209 | 210 | type unpacker func(int64, []interface{}) (messageInterface, error) 211 | 212 | var unpackers = map[int64]unpacker{ 213 | HANDSHAKE: unpackHandshake, 214 | HEARTBEAT: unpackHeartbeat, 215 | TERMINATE: unpackTerminate, 216 | INVOKE: unpackInvoke, 217 | CHUNK: unpackChunk, 218 | ERROR: unpackErrorMsg, 219 | CHOKE: unpackChoke, 220 | } 221 | 222 | // Common unpacker 223 | func unpackMessage(input []interface{}) (msg messageInterface, err error) { 224 | var session int64 225 | 226 | switch input[1].(type) { 227 | case uint64: 228 | session = int64(input[1].(uint64)) 229 | case int64: 230 | session = input[1].(int64) 231 | } 232 | 233 | unpacker, ok := unpackers[input[0].(int64)] 234 | 235 | if !ok { 236 | err = fmt.Errorf("cocaine: invalid message type: %d", input[0].(int64)) 237 | } 238 | 239 | data := input[2].([]interface{}) 240 | msg, err = unpacker(session, data) 241 | 242 | return 243 | } 244 | 245 | type streamUnpacker struct { 246 | buf []byte 247 | } 248 | 249 | func (unpacker *streamUnpacker) Feed(data []byte, logger LocalLogger) []messageInterface { 250 | var msgs []messageInterface 251 | unpacker.buf = append(unpacker.buf, data...) 252 | tmp := bytes.NewBuffer(unpacker.buf) 253 | dec := codec.NewDecoder(tmp, h) 254 | for { 255 | var res []interface{} 256 | err := dec.Decode(&res) 257 | if err != nil { 258 | if err != io.EOF { 259 | logger.Warnf("Decoding error: %v", err) 260 | } 261 | break 262 | } else { 263 | msg, err := unpackMessage(res) 264 | if err != nil { 265 | logger.Warnf("Unpacking error: %v", err) 266 | continue 267 | } 268 | msgs = append(msgs, msg) 269 | unpacker.buf = unpacker.buf[len(unpacker.buf)-tmp.Len():] 270 | } 271 | 272 | } 273 | return msgs 274 | } 275 | 276 | func newStreamUnpacker() *streamUnpacker { 277 | return &streamUnpacker{make([]byte, 0)} 278 | } 279 | -------------------------------------------------------------------------------- /cocaine12/asocket.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "net" 7 | "sync" 8 | "time" 9 | 10 | "github.com/ugorji/go/codec" 11 | ) 12 | 13 | var ( 14 | mhAsocket = codec.MsgpackHandle{ 15 | BasicHandle: codec.BasicHandle{ 16 | EncodeOptions: codec.EncodeOptions{ 17 | StructToArray: true, 18 | }, 19 | }, 20 | } 21 | hAsocket = &mhAsocket 22 | ) 23 | 24 | type asyncSender interface { 25 | Send(*Message) 26 | } 27 | 28 | type socketIO interface { 29 | asyncSender 30 | Read() chan *Message 31 | Write() chan *Message 32 | IsClosed() <-chan struct{} 33 | Close() 34 | } 35 | 36 | type asyncBuff struct { 37 | in chan *Message 38 | out chan *Message 39 | 40 | stop chan (<-chan time.Time) 41 | wait chan struct{} 42 | } 43 | 44 | func newAsyncBuf() *asyncBuff { 45 | buf := &asyncBuff{ 46 | in: make(chan *Message), 47 | out: make(chan *Message), 48 | 49 | // to stop my loop 50 | stop: make(chan (<-chan time.Time)), 51 | // to wait for a notifycation 52 | // from the loop that it's stopped 53 | wait: make(chan struct{}), 54 | } 55 | 56 | buf.loop() 57 | return buf 58 | } 59 | 60 | func (bf *asyncBuff) loop() { 61 | go func() { 62 | defer close(bf.wait) 63 | 64 | // Notify a receiver 65 | defer close(bf.out) 66 | 67 | var ( 68 | // buffer for messages 69 | pending []*Message 70 | 71 | // it should be read until closed 72 | // to get all messages from a sender 73 | input = bf.in 74 | 75 | // if <-chan time.Time is received we have to wait the buffer drainig 76 | // if closed return immediatly 77 | stopped = bf.stop 78 | 79 | quitAfterTimeout <-chan time.Time 80 | 81 | // empty buffer & the input is closed 82 | finished = false 83 | ) 84 | 85 | for { 86 | var ( 87 | candidate *Message 88 | out chan *Message 89 | ) 90 | 91 | if len(pending) > 0 { 92 | // mark the first message as a candidate to be sent 93 | // and unlock the sending state 94 | candidate = pending[0] 95 | out = bf.out 96 | } else if finished { 97 | // message queue is empty and 98 | // no more messages are expected 99 | return 100 | } 101 | 102 | select { 103 | // get a message from a sender 104 | case incoming, open := <-input: 105 | if open { 106 | pending = append(pending, incoming) 107 | } else { 108 | // Set the flag 109 | // Unset channel to lock the case 110 | finished = true 111 | input = nil 112 | } 113 | 114 | // send the first message from the queue to a reveiver 115 | case out <- candidate: 116 | pending = pending[1:] 117 | 118 | case timeoutChan, open := <-stopped: 119 | if !open { 120 | return 121 | } 122 | 123 | // Disable this case 124 | // to protect from Stop() after Drain() 125 | stopped = nil 126 | quitAfterTimeout = timeoutChan 127 | 128 | // it's usually nil channel, but 129 | // it can be set using passing a chan time.Time via stopped 130 | case <-quitAfterTimeout: 131 | return 132 | } 133 | } 134 | }() 135 | } 136 | 137 | // Stop stops a loop which is handling messages in the buffer 138 | // It is prohibited to call Drain afer Stop 139 | func (bf *asyncBuff) Stop() error { 140 | close(bf.stop) 141 | select { 142 | case <-bf.wait: 143 | case <-time.After(time.Second): 144 | } 145 | 146 | return nil 147 | } 148 | 149 | // Drain waits for the duration to let the buffer send pending messages. 150 | // It is prohibited to call Drain after Stop 151 | func (bf *asyncBuff) Drain(d time.Duration) error { 152 | var timeoutChan = time.After(d) 153 | select { 154 | case bf.stop <- timeoutChan: 155 | case <-timeoutChan: 156 | } 157 | return bf.Stop() 158 | } 159 | 160 | // Biderectional socket 161 | type asyncRWSocket struct { 162 | sync.Mutex 163 | conn io.ReadWriteCloser 164 | upstreamBuf *asyncBuff 165 | downstreamBuf *asyncBuff 166 | closed chan struct{} // broadcast channel 167 | } 168 | 169 | func newAsyncRW(conn io.ReadWriteCloser) (*asyncRWSocket, error) { 170 | sock := &asyncRWSocket{ 171 | conn: conn, 172 | upstreamBuf: newAsyncBuf(), 173 | downstreamBuf: newAsyncBuf(), 174 | closed: make(chan struct{}), 175 | } 176 | 177 | sock.readloop() 178 | sock.writeloop() 179 | 180 | return sock, nil 181 | } 182 | 183 | func newUnixConnection(address string, timeout time.Duration) (socketIO, error) { 184 | return newAsyncConnection("unix", address, timeout) 185 | } 186 | 187 | func newTCPConnection(address string, timeout time.Duration) (socketIO, error) { 188 | return newAsyncConnection("tcp", address, timeout) 189 | } 190 | 191 | func newAsyncConnection(family string, address string, timeout time.Duration) (socketIO, error) { 192 | dialer := net.Dialer{ 193 | Timeout: timeout, 194 | DualStack: true, 195 | } 196 | 197 | conn, err := dialer.Dial(family, address) 198 | if err != nil { 199 | return nil, err 200 | } 201 | return newAsyncRW(conn) 202 | } 203 | 204 | func (sock *asyncRWSocket) Close() { 205 | sock.upstreamBuf.Stop() 206 | sock.downstreamBuf.Stop() 207 | sock.close() 208 | } 209 | 210 | func (sock *asyncRWSocket) close() { 211 | sock.Lock() 212 | defer sock.Unlock() 213 | 214 | select { 215 | case <-sock.closed: // Already closed 216 | default: 217 | close(sock.closed) 218 | sock.conn.Close() 219 | } 220 | } 221 | 222 | func (sock *asyncRWSocket) IsClosed() (broadcast <-chan struct{}) { 223 | return sock.closed 224 | } 225 | 226 | func (sock *asyncRWSocket) Write() chan *Message { 227 | return sock.upstreamBuf.in 228 | } 229 | 230 | func (sock *asyncRWSocket) Read() chan *Message { 231 | return sock.downstreamBuf.out 232 | } 233 | 234 | func (sock *asyncRWSocket) Send(msg *Message) { 235 | select { 236 | case sock.Write() <- msg: 237 | case <-sock.IsClosed(): 238 | // Socket is in the closed state, 239 | // so drop the data 240 | } 241 | } 242 | 243 | func (sock *asyncRWSocket) writeloop() { 244 | go func() { 245 | var buf = bufio.NewWriter(sock.conn) 246 | encoder := codec.NewEncoder(buf, hAsocket) 247 | for incoming := range sock.upstreamBuf.out { 248 | err := encoder.Encode(incoming) 249 | if err != nil { 250 | sock.close() 251 | // blackhole all pending writes. See #31 252 | go func() { 253 | for _ = range sock.upstreamBuf.out { 254 | // pass 255 | } 256 | }() 257 | return 258 | } 259 | buf.Flush() 260 | } 261 | }() 262 | } 263 | 264 | func (sock *asyncRWSocket) readloop() { 265 | go func() { 266 | decoder := codec.NewDecoder(bufio.NewReader(sock.conn), hAsocket) 267 | for { 268 | var message *Message 269 | err := decoder.Decode(&message) 270 | if err != nil { 271 | close(sock.downstreamBuf.in) 272 | sock.close() 273 | return 274 | } 275 | sock.downstreamBuf.in <- message 276 | } 277 | }() 278 | } 279 | -------------------------------------------------------------------------------- /cocaine12/bridge/bridge.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "os/exec" 11 | "os/signal" 12 | "syscall" 13 | "time" 14 | 15 | cocaine "github.com/cocaine/cocaine-framework-go/cocaine12" 16 | ) 17 | 18 | type Bridge struct { 19 | config *BridgeConfig 20 | onClose chan struct{} 21 | 22 | worker *cocaine.Worker 23 | logger cocaine.Logger 24 | child *exec.Cmd 25 | } 26 | 27 | type BridgeConfig struct { 28 | Name string 29 | Args []string 30 | Env []string 31 | Port int 32 | StartupTimeout int 33 | } 34 | 35 | func (cfg *BridgeConfig) Endpoint() string { 36 | return fmt.Sprintf("localhost:%d", cfg.Port) 37 | } 38 | 39 | //Remove some cocaine-specific args 40 | func filterEndpointArg(args []string) []string { 41 | for i, arg := range args { 42 | if arg == "--endpoint" && len(args)-1 >= i+1 { 43 | // cut both the argname and value 44 | return append(args[:i], args[i+2:]...) 45 | } 46 | } 47 | 48 | return args 49 | } 50 | 51 | func NewBridgeConfig() *BridgeConfig { 52 | cfg := &BridgeConfig{ 53 | Name: "", 54 | Args: filterEndpointArg(os.Args[1:]), 55 | Env: os.Environ(), 56 | Port: 8080, 57 | StartupTimeout: 5, 58 | } 59 | return cfg 60 | } 61 | 62 | // to implement io.Writer 63 | type responseWriter struct { 64 | w cocaine.Response 65 | } 66 | 67 | func (r *responseWriter) Write(body []byte) (int, error) { 68 | r.w.Write(body) 69 | return len(body), nil 70 | } 71 | 72 | func NewBridge(cfg *BridgeConfig, logger cocaine.Logger) (*Bridge, error) { 73 | var worker *cocaine.Worker 74 | worker, err := cocaine.NewWorker() 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | endpoint := cfg.Endpoint() 80 | 81 | worker.SetFallbackHandler(func(ctx context.Context, event string, request cocaine.Request, response cocaine.Response) { 82 | defer response.Close() 83 | 84 | // Read the first chunk 85 | // It consists of method, uri, httpversion, headers, body. 86 | // They are packed by msgpack 87 | msg, err := request.Read(ctx) 88 | if err != nil { 89 | if ctx.Err() != nil { 90 | response.Write(cocaine.WriteHead(http.StatusRequestTimeout, cocaine.Headers{})) 91 | response.Write([]byte("request was not received during a timeout")) 92 | return 93 | } 94 | 95 | response.Write(cocaine.WriteHead(http.StatusBadRequest, cocaine.Headers{})) 96 | response.Write([]byte("cannot process request " + err.Error())) 97 | return 98 | } 99 | 100 | httpRequest, err := cocaine.UnpackProxyRequest(msg) 101 | if err != nil { 102 | response.Write(cocaine.WriteHead(http.StatusBadRequest, cocaine.Headers{})) 103 | response.Write([]byte(fmt.Sprintf("malformed request: %v", err))) 104 | return 105 | } 106 | 107 | // Set scheme and endpoint 108 | httpRequest.URL.Scheme = "http" 109 | httpRequest.URL.Host = endpoint 110 | 111 | appResp, err := http.DefaultClient.Do(httpRequest) 112 | if err != nil { 113 | response.Write(cocaine.WriteHead(http.StatusInternalServerError, cocaine.Headers{})) 114 | response.Write([]byte(fmt.Sprintf("unable to proxy a request: %v", err))) 115 | return 116 | } 117 | defer appResp.Body.Close() 118 | 119 | response.Write(cocaine.WriteHead(appResp.StatusCode, cocaine.HeadersHTTPtoCocaine(appResp.Header))) 120 | 121 | io.Copy(&responseWriter{response}, appResp.Body) 122 | }) 123 | 124 | child := exec.Command(cfg.Name, cfg.Args...) 125 | // attach environment 126 | child.Env = cfg.Env 127 | // the child will be killed if the master died 128 | child.SysProcAttr = getSysProctAttr() 129 | 130 | b := &Bridge{ 131 | config: cfg, 132 | worker: worker, 133 | logger: logger, 134 | child: child, 135 | onClose: make(chan struct{}), 136 | } 137 | 138 | go b.eventLoop() 139 | 140 | return b, nil 141 | } 142 | 143 | func (b *Bridge) Start() error { 144 | onClose := make(chan struct{}) 145 | defer close(onClose) 146 | go func() { 147 | deadline := time.After(time.Duration(b.config.StartupTimeout) * time.Second) 148 | endpoint := b.config.Endpoint() 149 | PING_LOOP: 150 | for { 151 | if err := ping(endpoint, time.Millisecond*100); err == nil { 152 | break PING_LOOP 153 | } 154 | 155 | select { 156 | case <-deadline: 157 | return 158 | case <-onClose: 159 | return 160 | default: 161 | } 162 | } 163 | b.worker.Run(nil) 164 | }() 165 | 166 | stdout, err := b.child.StdoutPipe() 167 | if err != nil { 168 | return err 169 | } 170 | 171 | stderr, err := b.child.StderrPipe() 172 | if err != nil { 173 | return err 174 | } 175 | 176 | // ToDo: make this goroutines terminatable 177 | go func() { 178 | buf := bufio.NewScanner(stdout) 179 | for buf.Scan() { 180 | b.logger.Infof("%s", buf.Bytes()) 181 | } 182 | 183 | if err := buf.Err(); err != nil { 184 | b.logger.Errf("unable to read stdout %v", err) 185 | } 186 | }() 187 | 188 | go func() { 189 | buf := bufio.NewScanner(stderr) 190 | for buf.Scan() { 191 | b.logger.Infof("%s", buf.Bytes()) 192 | } 193 | 194 | if err := buf.Err(); err != nil { 195 | b.logger.Errf("unable to read stderr: %v", err) 196 | } 197 | }() 198 | 199 | if err := b.child.Start(); err != nil { 200 | return err 201 | } 202 | 203 | return b.child.Wait() 204 | } 205 | 206 | func (b *Bridge) eventLoop() { 207 | // create a watcher 208 | signalWatcher := make(chan os.Signal, 10) 209 | signalCHLDWatcher := make(chan os.Signal, 1) 210 | // attach the signal watcher to watch the signals 211 | signal.Notify(signalWatcher, syscall.SIGINT, syscall.SIGTERM, syscall.SIGSTOP) 212 | signal.Notify(signalCHLDWatcher, syscall.SIGCHLD) 213 | 214 | var stopChild = true 215 | 216 | for { 217 | select { 218 | case <-signalCHLDWatcher: 219 | // we should stop the worker only 220 | stopChild = false 221 | case <-signalWatcher: 222 | // pass stopChild 223 | case <-b.onClose: 224 | // pass to stopChild 225 | } 226 | } 227 | 228 | b.stopWorker() 229 | 230 | if stopChild { 231 | b.stopChild() 232 | } 233 | } 234 | 235 | func (b *Bridge) stopChild() error { 236 | if err := b.child.Process.Signal(syscall.SIGTERM); err != nil { 237 | b.child.Process.Kill() 238 | return err 239 | } 240 | 241 | // not to stuck in waiting for a child process 242 | // we will kill it if it takes too long 243 | notWait := make(chan struct{}) 244 | go func() { 245 | select { 246 | case <-time.After(time.Second * 5): 247 | b.child.Process.Kill() 248 | case <-notWait: 249 | return 250 | } 251 | }() 252 | 253 | err := b.child.Wait() 254 | close(notWait) 255 | 256 | return err 257 | } 258 | 259 | func (b *Bridge) stopWorker() { 260 | b.worker.Stop() 261 | } 262 | 263 | func (b *Bridge) Close() { 264 | // notify the eventLoop 265 | // about an intention to be stopped 266 | // and wait for an exit of the child process 267 | close(b.onClose) 268 | b.waitChild() 269 | } 270 | 271 | func (b *Bridge) waitChild() { 272 | b.child.Process.Wait() 273 | } 274 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/codec/time.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, 2013 Ugorji Nwoke. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license found in the LICENSE file. 3 | 4 | package codec 5 | 6 | import ( 7 | "time" 8 | ) 9 | 10 | var ( 11 | timeDigits = [...]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} 12 | ) 13 | 14 | // EncodeTime encodes a time.Time as a []byte, including 15 | // information on the instant in time and UTC offset. 16 | // 17 | // Format Description 18 | // 19 | // A timestamp is composed of 3 components: 20 | // 21 | // - secs: signed integer representing seconds since unix epoch 22 | // - nsces: unsigned integer representing fractional seconds as a 23 | // nanosecond offset within secs, in the range 0 <= nsecs < 1e9 24 | // - tz: signed integer representing timezone offset in minutes east of UTC, 25 | // and a dst (daylight savings time) flag 26 | // 27 | // When encoding a timestamp, the first byte is the descriptor, which 28 | // defines which components are encoded and how many bytes are used to 29 | // encode secs and nsecs components. *If secs/nsecs is 0 or tz is UTC, it 30 | // is not encoded in the byte array explicitly*. 31 | // 32 | // Descriptor 8 bits are of the form `A B C DDD EE`: 33 | // A: Is secs component encoded? 1 = true 34 | // B: Is nsecs component encoded? 1 = true 35 | // C: Is tz component encoded? 1 = true 36 | // DDD: Number of extra bytes for secs (range 0-7). 37 | // If A = 1, secs encoded in DDD+1 bytes. 38 | // If A = 0, secs is not encoded, and is assumed to be 0. 39 | // If A = 1, then we need at least 1 byte to encode secs. 40 | // DDD says the number of extra bytes beyond that 1. 41 | // E.g. if DDD=0, then secs is represented in 1 byte. 42 | // if DDD=2, then secs is represented in 3 bytes. 43 | // EE: Number of extra bytes for nsecs (range 0-3). 44 | // If B = 1, nsecs encoded in EE+1 bytes (similar to secs/DDD above) 45 | // 46 | // Following the descriptor bytes, subsequent bytes are: 47 | // 48 | // secs component encoded in `DDD + 1` bytes (if A == 1) 49 | // nsecs component encoded in `EE + 1` bytes (if B == 1) 50 | // tz component encoded in 2 bytes (if C == 1) 51 | // 52 | // secs and nsecs components are integers encoded in a BigEndian 53 | // 2-complement encoding format. 54 | // 55 | // tz component is encoded as 2 bytes (16 bits). Most significant bit 15 to 56 | // Least significant bit 0 are described below: 57 | // 58 | // Timezone offset has a range of -12:00 to +14:00 (ie -720 to +840 minutes). 59 | // Bit 15 = have\_dst: set to 1 if we set the dst flag. 60 | // Bit 14 = dst\_on: set to 1 if dst is in effect at the time, or 0 if not. 61 | // Bits 13..0 = timezone offset in minutes. It is a signed integer in Big Endian format. 62 | // 63 | func encodeTime(t time.Time) []byte { 64 | //t := rv.Interface().(time.Time) 65 | tsecs, tnsecs := t.Unix(), t.Nanosecond() 66 | var ( 67 | bd byte 68 | btmp [8]byte 69 | bs [16]byte 70 | i int = 1 71 | ) 72 | l := t.Location() 73 | if l == time.UTC { 74 | l = nil 75 | } 76 | if tsecs != 0 { 77 | bd = bd | 0x80 78 | bigen.PutUint64(btmp[:], uint64(tsecs)) 79 | f := pruneSignExt(btmp[:], tsecs >= 0) 80 | bd = bd | (byte(7-f) << 2) 81 | copy(bs[i:], btmp[f:]) 82 | i = i + (8 - f) 83 | } 84 | if tnsecs != 0 { 85 | bd = bd | 0x40 86 | bigen.PutUint32(btmp[:4], uint32(tnsecs)) 87 | f := pruneSignExt(btmp[:4], true) 88 | bd = bd | byte(3-f) 89 | copy(bs[i:], btmp[f:4]) 90 | i = i + (4 - f) 91 | } 92 | if l != nil { 93 | bd = bd | 0x20 94 | // Note that Go Libs do not give access to dst flag. 95 | _, zoneOffset := t.Zone() 96 | //zoneName, zoneOffset := t.Zone() 97 | zoneOffset /= 60 98 | z := uint16(zoneOffset) 99 | bigen.PutUint16(btmp[:2], z) 100 | // clear dst flags 101 | bs[i] = btmp[0] & 0x3f 102 | bs[i+1] = btmp[1] 103 | i = i + 2 104 | } 105 | bs[0] = bd 106 | return bs[0:i] 107 | } 108 | 109 | // DecodeTime decodes a []byte into a time.Time. 110 | func decodeTime(bs []byte) (tt time.Time, err error) { 111 | bd := bs[0] 112 | var ( 113 | tsec int64 114 | tnsec uint32 115 | tz uint16 116 | i byte = 1 117 | i2 byte 118 | n byte 119 | ) 120 | if bd&(1<<7) != 0 { 121 | var btmp [8]byte 122 | n = ((bd >> 2) & 0x7) + 1 123 | i2 = i + n 124 | copy(btmp[8-n:], bs[i:i2]) 125 | //if first bit of bs[i] is set, then fill btmp[0..8-n] with 0xff (ie sign extend it) 126 | if bs[i]&(1<<7) != 0 { 127 | copy(btmp[0:8-n], bsAll0xff) 128 | //for j,k := byte(0), 8-n; j < k; j++ { btmp[j] = 0xff } 129 | } 130 | i = i2 131 | tsec = int64(bigen.Uint64(btmp[:])) 132 | } 133 | if bd&(1<<6) != 0 { 134 | var btmp [4]byte 135 | n = (bd & 0x3) + 1 136 | i2 = i + n 137 | copy(btmp[4-n:], bs[i:i2]) 138 | i = i2 139 | tnsec = bigen.Uint32(btmp[:]) 140 | } 141 | if bd&(1<<5) == 0 { 142 | tt = time.Unix(tsec, int64(tnsec)).UTC() 143 | return 144 | } 145 | // In stdlib time.Parse, when a date is parsed without a zone name, it uses "" as zone name. 146 | // However, we need name here, so it can be shown when time is printed. 147 | // Zone name is in form: UTC-08:00. 148 | // Note that Go Libs do not give access to dst flag, so we ignore dst bits 149 | 150 | i2 = i + 2 151 | tz = bigen.Uint16(bs[i:i2]) 152 | i = i2 153 | // sign extend sign bit into top 2 MSB (which were dst bits): 154 | if tz&(1<<13) == 0 { // positive 155 | tz = tz & 0x3fff //clear 2 MSBs: dst bits 156 | } else { // negative 157 | tz = tz | 0xc000 //set 2 MSBs: dst bits 158 | //tzname[3] = '-' (TODO: verify. this works here) 159 | } 160 | tzint := int16(tz) 161 | if tzint == 0 { 162 | tt = time.Unix(tsec, int64(tnsec)).UTC() 163 | } else { 164 | // For Go Time, do not use a descriptive timezone. 165 | // It's unnecessary, and makes it harder to do a reflect.DeepEqual. 166 | // The Offset already tells what the offset should be, if not on UTC and unknown zone name. 167 | // var zoneName = timeLocUTCName(tzint) 168 | tt = time.Unix(tsec, int64(tnsec)).In(time.FixedZone("", int(tzint)*60)) 169 | } 170 | return 171 | } 172 | 173 | func timeLocUTCName(tzint int16) string { 174 | if tzint == 0 { 175 | return "UTC" 176 | } 177 | var tzname = []byte("UTC+00:00") 178 | //tzname := fmt.Sprintf("UTC%s%02d:%02d", tzsign, tz/60, tz%60) //perf issue using Sprintf. inline below. 179 | //tzhr, tzmin := tz/60, tz%60 //faster if u convert to int first 180 | var tzhr, tzmin int16 181 | if tzint < 0 { 182 | tzname[3] = '-' // (TODO: verify. this works here) 183 | tzhr, tzmin = -tzint/60, (-tzint)%60 184 | } else { 185 | tzhr, tzmin = tzint/60, tzint%60 186 | } 187 | tzname[4] = timeDigits[tzhr/10] 188 | tzname[5] = timeDigits[tzhr%10] 189 | tzname[7] = timeDigits[tzmin/10] 190 | tzname[8] = timeDigits[tzmin%10] 191 | return string(tzname) 192 | //return time.FixedZone(string(tzname), int(tzint)*60) 193 | } 194 | -------------------------------------------------------------------------------- /cocaine/asocket.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "time" 7 | 8 | "log" 9 | ) 10 | 11 | var _ = log.Println 12 | 13 | type LocalLogger interface { 14 | Debug(args ...interface{}) 15 | Debugf(msg string, args ...interface{}) 16 | Info(args ...interface{}) 17 | Infof(msg string, args ...interface{}) 18 | Warn(args ...interface{}) 19 | Warnf(msg string, args ...interface{}) 20 | Err(args ...interface{}) 21 | Errf(msg string, args ...interface{}) 22 | } 23 | 24 | type LocalLoggerImpl struct{} 25 | 26 | func (l *LocalLoggerImpl) Debug(args ...interface{}) { /*pass*/ } 27 | func (l *LocalLoggerImpl) Debugf(msg string, args ...interface{}) { /*pass*/ } 28 | func (l *LocalLoggerImpl) Info(args ...interface{}) { /*pass*/ } 29 | func (l *LocalLoggerImpl) Infof(msg string, args ...interface{}) { /*pass*/ } 30 | func (l *LocalLoggerImpl) Warn(args ...interface{}) { /*pass*/ } 31 | func (l *LocalLoggerImpl) Warnf(msg string, args ...interface{}) { /*pass*/ } 32 | func (l *LocalLoggerImpl) Err(args ...interface{}) { /*pass*/ } 33 | func (l *LocalLoggerImpl) Errf(msg string, args ...interface{}) { /*pass*/ } 34 | 35 | type socketIO interface { 36 | Read() chan rawMessage 37 | Write() chan rawMessage 38 | IsClosed() <-chan struct{} 39 | Close() 40 | } 41 | 42 | type socketWriter interface { 43 | Write() chan rawMessage 44 | IsClosed() <-chan struct{} 45 | Close() 46 | } 47 | 48 | type asyncBuff struct { 49 | in, out chan rawMessage 50 | stop chan bool 51 | wg sync.WaitGroup 52 | } 53 | 54 | func newAsyncBuf() *asyncBuff { 55 | buf := asyncBuff{make(chan rawMessage), make(chan rawMessage), make(chan bool, 1), sync.WaitGroup{}} 56 | buf.loop() 57 | return &buf 58 | } 59 | 60 | func (bf *asyncBuff) loop() { 61 | go func() { 62 | bf.wg.Add(1) 63 | defer bf.wg.Done() 64 | var pending []rawMessage // data buffer 65 | var _in chan rawMessage // incoming channel 66 | _in = bf.in 67 | 68 | finished := false // flag 69 | for { 70 | var first rawMessage 71 | var _out chan rawMessage 72 | if len(pending) > 0 { 73 | first = pending[0] 74 | _out = bf.out 75 | } else if finished { 76 | close(bf.out) 77 | break 78 | } 79 | select { 80 | case incoming, ok := <-_in: 81 | if ok { 82 | pending = append(pending, incoming) 83 | } else { 84 | finished = true 85 | _in = nil 86 | } 87 | case _out <- first: 88 | pending[0] = nil 89 | pending = pending[1:] 90 | case <-bf.stop: 91 | close(bf.out) // Notify receiver 92 | return 93 | } 94 | } 95 | }() 96 | } 97 | 98 | func (bf *asyncBuff) Stop() (res bool) { 99 | close(bf.stop) 100 | bf.wg.Wait() 101 | return 102 | } 103 | 104 | // Biderectional socket 105 | type asyncRWSocket struct { 106 | net.Conn 107 | clientToSock *asyncBuff 108 | socketToClient *asyncBuff 109 | closed chan struct{} //broadcast channel 110 | mutex sync.Mutex 111 | logger LocalLogger 112 | } 113 | 114 | func newAsyncRWSocket(family string, address string, timeout time.Duration, logger LocalLogger) (*asyncRWSocket, error) { 115 | dialer := net.Dialer{ 116 | Timeout: timeout, 117 | DualStack: true, 118 | } 119 | 120 | conn, err := dialer.Dial(family, address) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | sock := asyncRWSocket{conn, newAsyncBuf(), newAsyncBuf(), make(chan struct{}), sync.Mutex{}, logger} 126 | sock.readloop() 127 | sock.writeloop() 128 | return &sock, nil 129 | } 130 | 131 | func (sock *asyncRWSocket) Close() { 132 | sock.clientToSock.Stop() 133 | sock.socketToClient.Stop() 134 | sock.close() 135 | } 136 | 137 | func (sock *asyncRWSocket) close() bool { 138 | sock.mutex.Lock() 139 | defer sock.mutex.Unlock() 140 | select { 141 | case <-sock.closed: // Already closed 142 | default: 143 | close(sock.closed) 144 | sock.Conn.Close() 145 | return true 146 | } 147 | return false 148 | } 149 | 150 | func (sock *asyncRWSocket) IsClosed() (broadcast <-chan struct{}) { 151 | return sock.closed 152 | } 153 | 154 | func (sock *asyncRWSocket) Write() chan rawMessage { 155 | return sock.clientToSock.in 156 | } 157 | 158 | func (sock *asyncRWSocket) Read() chan rawMessage { 159 | return sock.socketToClient.out 160 | } 161 | 162 | func (sock *asyncRWSocket) writeloop() { 163 | go func() { 164 | for incoming := range sock.clientToSock.out { 165 | _, err := sock.Conn.Write(incoming) //Add check for sending full 166 | if err != nil { 167 | sock.logger.Errf("Error while writing data: %v", err) 168 | sock.close() 169 | // blackhole all writes 170 | go func() { 171 | ok := true 172 | for ok { 173 | _, ok = <-sock.clientToSock.out 174 | } 175 | }() 176 | return 177 | } 178 | } 179 | }() 180 | } 181 | 182 | func (sock *asyncRWSocket) readloop() { 183 | go func() { 184 | buf := make([]byte, 65536) 185 | for { 186 | count, err := sock.Conn.Read(buf) 187 | if count > 0 { 188 | bufferToSend := make([]byte, count) 189 | copy(bufferToSend[:], buf[:count]) 190 | sock.socketToClient.in <- bufferToSend 191 | } 192 | 193 | if err != nil { 194 | close(sock.socketToClient.in) 195 | if sock.close() { 196 | // pass errors like "use of closed network connection" 197 | sock.logger.Errf("Error while reading data: %v", err) 198 | } 199 | return 200 | } 201 | } 202 | }() 203 | } 204 | 205 | // WriteOnly Socket 206 | type asyncWSocket struct { 207 | net.Conn 208 | clientToSock *asyncBuff 209 | closed chan struct{} //broadcast channel 210 | mutex sync.Mutex 211 | logger LocalLogger 212 | } 213 | 214 | func newWSocket(family string, address string, timeout time.Duration, logger LocalLogger) (*asyncWSocket, error) { 215 | conn, err := net.DialTimeout(family, address, timeout) 216 | if err != nil { 217 | return nil, err 218 | } 219 | 220 | sock := asyncWSocket{conn, newAsyncBuf(), make(chan struct{}), sync.Mutex{}, logger} 221 | sock.readloop() 222 | sock.writeloop() 223 | return &sock, nil 224 | } 225 | 226 | func (sock *asyncWSocket) Write() chan rawMessage { 227 | return sock.clientToSock.in 228 | } 229 | 230 | func (sock *asyncWSocket) close() bool { 231 | sock.mutex.Lock() // Is it really necessary??? 232 | defer sock.mutex.Unlock() 233 | select { 234 | case <-sock.closed: // Already closed 235 | default: 236 | close(sock.closed) 237 | sock.Conn.Close() 238 | return true 239 | } 240 | return false 241 | } 242 | 243 | func (sock *asyncWSocket) Close() { 244 | sock.close() 245 | sock.clientToSock.Stop() 246 | } 247 | 248 | func (sock *asyncWSocket) IsClosed() (broadcast <-chan struct{}) { 249 | return sock.closed 250 | } 251 | 252 | func (sock *asyncWSocket) writeloop() { 253 | go func() { 254 | for incoming := range sock.clientToSock.out { 255 | _, err := sock.Conn.Write(incoming) //Add check for sending full 256 | if err != nil { 257 | sock.logger.Errf("Error while writing data: %v", err) 258 | sock.close() 259 | return 260 | } 261 | } 262 | }() 263 | } 264 | 265 | func (sock *asyncWSocket) readloop() { 266 | go func() { 267 | buf := make([]byte, 2048) 268 | for { 269 | _, err := sock.Conn.Read(buf) 270 | if err != nil { 271 | if sock.close() { 272 | // pass errors like "use of closed network connection" 273 | sock.logger.Errf("Error while reading data: %v", err) 274 | } 275 | return 276 | } 277 | } 278 | }() 279 | } 280 | -------------------------------------------------------------------------------- /vendor/github.com/ugorji/go/codec/README.md: -------------------------------------------------------------------------------- 1 | # Codec 2 | 3 | High Performance and Feature-Rich Idiomatic Go Library providing 4 | encode/decode support for different serialization formats. 5 | 6 | Supported Serialization formats are: 7 | 8 | - msgpack: [https://github.com/msgpack/msgpack] 9 | - binc: [http://github.com/ugorji/binc] 10 | 11 | To install: 12 | 13 | go get github.com/ugorji/go/codec 14 | 15 | Online documentation: [http://godoc.org/github.com/ugorji/go/codec] 16 | 17 | The idiomatic Go support is as seen in other encoding packages in 18 | the standard library (ie json, xml, gob, etc). 19 | 20 | Rich Feature Set includes: 21 | 22 | - Simple but extremely powerful and feature-rich API 23 | - Very High Performance. 24 | Our extensive benchmarks show us outperforming Gob, Json and Bson by 2-4X. 25 | This was achieved by taking extreme care on: 26 | - managing allocation 27 | - function frame size (important due to Go's use of split stacks), 28 | - reflection use (and by-passing reflection for common types) 29 | - recursion implications 30 | - zero-copy mode (encoding/decoding to byte slice without using temp buffers) 31 | - Correct. 32 | Care was taken to precisely handle corner cases like: 33 | overflows, nil maps and slices, nil value in stream, etc. 34 | - Efficient zero-copying into temporary byte buffers 35 | when encoding into or decoding from a byte slice. 36 | - Standard field renaming via tags 37 | - Encoding from any value 38 | (struct, slice, map, primitives, pointers, interface{}, etc) 39 | - Decoding into pointer to any non-nil typed value 40 | (struct, slice, map, int, float32, bool, string, reflect.Value, etc) 41 | - Supports extension functions to handle the encode/decode of custom types 42 | - Support Go 1.2 encoding.BinaryMarshaler/BinaryUnmarshaler 43 | - Schema-less decoding 44 | (decode into a pointer to a nil interface{} as opposed to a typed non-nil value). 45 | Includes Options to configure what specific map or slice type to use 46 | when decoding an encoded list or map into a nil interface{} 47 | - Provides a RPC Server and Client Codec for net/rpc communication protocol. 48 | - Msgpack Specific: 49 | - Provides extension functions to handle spec-defined extensions (binary, timestamp) 50 | - Options to resolve ambiguities in handling raw bytes (as string or []byte) 51 | during schema-less decoding (decoding into a nil interface{}) 52 | - RPC Server/Client Codec for msgpack-rpc protocol defined at: 53 | https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md 54 | - Fast Paths for some container types: 55 | For some container types, we circumvent reflection and its associated overhead 56 | and allocation costs, and encode/decode directly. These types are: 57 | []interface{} 58 | []int 59 | []string 60 | map[interface{}]interface{} 61 | map[int]interface{} 62 | map[string]interface{} 63 | 64 | ## Extension Support 65 | 66 | Users can register a function to handle the encoding or decoding of 67 | their custom types. 68 | 69 | There are no restrictions on what the custom type can be. Some examples: 70 | 71 | type BisSet []int 72 | type BitSet64 uint64 73 | type UUID string 74 | type MyStructWithUnexportedFields struct { a int; b bool; c []int; } 75 | type GifImage struct { ... } 76 | 77 | As an illustration, MyStructWithUnexportedFields would normally be 78 | encoded as an empty map because it has no exported fields, while UUID 79 | would be encoded as a string. However, with extension support, you can 80 | encode any of these however you like. 81 | 82 | ## RPC 83 | 84 | RPC Client and Server Codecs are implemented, so the codecs can be used 85 | with the standard net/rpc package. 86 | 87 | ## Usage 88 | 89 | Typical usage model: 90 | 91 | // create and configure Handle 92 | var ( 93 | bh codec.BincHandle 94 | mh codec.MsgpackHandle 95 | ) 96 | 97 | mh.MapType = reflect.TypeOf(map[string]interface{}(nil)) 98 | 99 | // configure extensions 100 | // e.g. for msgpack, define functions and enable Time support for tag 1 101 | // mh.AddExt(reflect.TypeOf(time.Time{}), 1, myMsgpackTimeEncodeExtFn, myMsgpackTimeDecodeExtFn) 102 | 103 | // create and use decoder/encoder 104 | var ( 105 | r io.Reader 106 | w io.Writer 107 | b []byte 108 | h = &bh // or mh to use msgpack 109 | ) 110 | 111 | dec = codec.NewDecoder(r, h) 112 | dec = codec.NewDecoderBytes(b, h) 113 | err = dec.Decode(&v) 114 | 115 | enc = codec.NewEncoder(w, h) 116 | enc = codec.NewEncoderBytes(&b, h) 117 | err = enc.Encode(v) 118 | 119 | //RPC Server 120 | go func() { 121 | for { 122 | conn, err := listener.Accept() 123 | rpcCodec := codec.GoRpc.ServerCodec(conn, h) 124 | //OR rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, h) 125 | rpc.ServeCodec(rpcCodec) 126 | } 127 | }() 128 | 129 | //RPC Communication (client side) 130 | conn, err = net.Dial("tcp", "localhost:5555") 131 | rpcCodec := codec.GoRpc.ClientCodec(conn, h) 132 | //OR rpcCodec := codec.MsgpackSpecRpc.ClientCodec(conn, h) 133 | client := rpc.NewClientWithCodec(rpcCodec) 134 | 135 | ## Representative Benchmark Results 136 | 137 | A sample run of benchmark using "go test -bi -bench=. -benchmem": 138 | 139 | /proc/cpuinfo: Intel(R) Core(TM) i7-2630QM CPU @ 2.00GHz (HT) 140 | 141 | .............................................. 142 | BENCHMARK INIT: 2013-10-16 11:02:50.345970786 -0400 EDT 143 | To run full benchmark comparing encodings (MsgPack, Binc, JSON, GOB, etc), use: "go test -bench=." 144 | Benchmark: 145 | Struct recursive Depth: 1 146 | ApproxDeepSize Of benchmark Struct: 4694 bytes 147 | Benchmark One-Pass Run: 148 | v-msgpack: len: 1600 bytes 149 | bson: len: 3025 bytes 150 | msgpack: len: 1560 bytes 151 | binc: len: 1187 bytes 152 | gob: len: 1972 bytes 153 | json: len: 2538 bytes 154 | .............................................. 155 | PASS 156 | Benchmark__Msgpack____Encode 50000 54359 ns/op 14953 B/op 83 allocs/op 157 | Benchmark__Msgpack____Decode 10000 106531 ns/op 14990 B/op 410 allocs/op 158 | Benchmark__Binc_NoSym_Encode 50000 53956 ns/op 14966 B/op 83 allocs/op 159 | Benchmark__Binc_NoSym_Decode 10000 103751 ns/op 14529 B/op 386 allocs/op 160 | Benchmark__Binc_Sym___Encode 50000 65961 ns/op 17130 B/op 88 allocs/op 161 | Benchmark__Binc_Sym___Decode 10000 106310 ns/op 15857 B/op 287 allocs/op 162 | Benchmark__Gob________Encode 10000 135944 ns/op 21189 B/op 237 allocs/op 163 | Benchmark__Gob________Decode 5000 405390 ns/op 83460 B/op 1841 allocs/op 164 | Benchmark__Json_______Encode 20000 79412 ns/op 13874 B/op 102 allocs/op 165 | Benchmark__Json_______Decode 10000 247979 ns/op 14202 B/op 493 allocs/op 166 | Benchmark__Bson_______Encode 10000 121762 ns/op 27814 B/op 514 allocs/op 167 | Benchmark__Bson_______Decode 10000 162126 ns/op 16514 B/op 789 allocs/op 168 | Benchmark__VMsgpack___Encode 50000 69155 ns/op 12370 B/op 344 allocs/op 169 | Benchmark__VMsgpack___Decode 10000 151609 ns/op 20307 B/op 571 allocs/op 170 | ok ugorji.net/codec 30.827s 171 | 172 | To run full benchmark suite (including against vmsgpack and bson), 173 | see notes in ext\_dep\_test.go 174 | 175 | -------------------------------------------------------------------------------- /cocaine12/httphandler.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "context" 7 | "io" 8 | "net/http" 9 | 10 | "github.com/ugorji/go/codec" 11 | ) 12 | 13 | var ( 14 | mh codec.MsgpackHandle 15 | h = &mh 16 | ) 17 | 18 | var ( 19 | mhHTTPReq codec.MsgpackHandle 20 | hHTTPReq = &mhHTTPReq 21 | ) 22 | 23 | // Headers are packed as a tuple of tuples HTTP headers from Cocaine 24 | type Headers [][2]string 25 | 26 | // type HTTPReq struct { 27 | // *http.Request 28 | // } 29 | 30 | // UnpackProxyRequest unpacks a HTTPRequest from a serialized cocaine form 31 | func UnpackProxyRequest(raw []byte) (*http.Request, error) { 32 | var v struct { 33 | Method string 34 | URI string 35 | Version string 36 | Headers Headers 37 | Body []byte 38 | } 39 | 40 | if err := codec.NewDecoderBytes(raw, hHTTPReq).Decode(&v); err != nil { 41 | return nil, err 42 | } 43 | 44 | req, err := http.NewRequest(v.Method, v.URI, bytes.NewBuffer(v.Body)) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | req.Header = HeadersCocaineToHTTP(v.Headers) 50 | req.Host = req.Header.Get("Host") 51 | 52 | if xRealIP := req.Header.Get("X-Real-IP"); xRealIP != "" { 53 | req.RemoteAddr = xRealIP 54 | } 55 | 56 | // If body is compressed it will be decompressed 57 | // Inspired by https://github.com/golang/go/blob/master/src/net/http/transport.go#L883 58 | hasBody := req != nil && req.Method != "HEAD" && req.ContentLength != 0 59 | if hasBody && req.Header.Get("Content-Encoding") == "gzip" { 60 | req.Header.Del("Content-Encoding") 61 | req.Header.Del("Content-Length") 62 | req.ContentLength = -1 63 | req.Body = &gzipReader{body: req.Body} 64 | } 65 | 66 | return req, nil 67 | } 68 | 69 | // WriteHead converts the HTTP status code and the headers to the cocaine format 70 | func WriteHead(code int, headers Headers) []byte { 71 | var out []byte 72 | codec.NewEncoderBytes(&out, h).Encode([]interface{}{code, headers}) 73 | return out 74 | } 75 | 76 | // HeadersHTTPtoCocaine converts net/http.Header to Cocaine representation 77 | func HeadersHTTPtoCocaine(header http.Header) Headers { 78 | hdr := make(Headers, 0, len(header)) 79 | for headerName, headerValues := range header { 80 | for _, headerValue := range headerValues { 81 | hdr = append(hdr, [2]string{headerName, headerValue}) 82 | } 83 | } 84 | return hdr 85 | } 86 | 87 | // HeadersCocaineToHTTP converts Cocaine representation of headers to net/http.Header 88 | func HeadersCocaineToHTTP(hdr Headers) http.Header { 89 | header := make(http.Header, len(hdr)) 90 | for _, hdrValues := range hdr { 91 | header.Add(hdrValues[0], hdrValues[1]) 92 | } 93 | return header 94 | } 95 | 96 | // WrapHandler provides opportunity for using Go web frameworks, which supports http.Handler interface 97 | // 98 | // Trivial example which is used martini web framework 99 | // 100 | // import ( 101 | // "github.com/cocaine/cocaine-framework-go/cocaine" 102 | // "github.com/codegangsta/martini" 103 | // ) 104 | // 105 | // 106 | // func main() { 107 | // m := martini.Classic() 108 | // m.Get("", func() string { 109 | // return "This is an example server" 110 | // }) 111 | // 112 | // m.Get("/hw", func() string { 113 | // return "Hello world!" 114 | // }) 115 | // 116 | // binds := map[string]cocaine.EventHandler{ 117 | // "example": cocaine.WrapHandler(m, nil), 118 | // } 119 | // if worker, err := cocaine.NewWorker(); err == nil{ 120 | // worker.Loop(binds) 121 | // }else{ 122 | // panic(err) 123 | // } 124 | // } 125 | func WrapHandler(handler http.Handler) EventHandler { 126 | return func(ctx context.Context, request Request, response Response) { 127 | defer response.Close() 128 | 129 | w, httpRequest, err := convertToHTTPFunc(ctx, request, response) 130 | if err != nil { 131 | return 132 | } 133 | 134 | handler.ServeHTTP(w, httpRequest) 135 | w.finishRequest() 136 | } 137 | } 138 | 139 | func WrapHTTPFunc(handler func(ctx context.Context, w http.ResponseWriter, req *http.Request)) EventHandler { 140 | return func(ctx context.Context, request Request, response Response) { 141 | defer response.Close() 142 | 143 | w, httpRequest, err := convertToHTTPFunc(ctx, request, response) 144 | if err != nil { 145 | return 146 | } 147 | 148 | handler(ctx, w, httpRequest) 149 | w.finishRequest() 150 | } 151 | } 152 | 153 | func WrapHTTPFuncs(hfs map[string]func(ctx context.Context, w http.ResponseWriter, req *http.Request)) map[string]EventHandler { 154 | handlers := make(map[string]EventHandler, len(hfs)) 155 | for key, hf := range hfs { 156 | handlers[key] = WrapHTTPFunc(hf) 157 | } 158 | 159 | return handlers 160 | } 161 | 162 | func convertToHTTPFunc(ctx context.Context, request Request, response Response) (*ResponseWriter, *http.Request, error) { 163 | // Read the first chunk 164 | // It consists of method, uri, httpversion, headers, body. 165 | // They are packed by msgpack 166 | msg, err := request.Read(ctx) 167 | if err != nil { 168 | if ctx.Err() != nil { 169 | response.Write(WriteHead(http.StatusRequestTimeout, Headers{})) 170 | response.Write([]byte("request was not received during a timeout")) 171 | return nil, nil, ctx.Err() 172 | } 173 | 174 | response.Write(WriteHead(http.StatusBadRequest, Headers{})) 175 | response.Write([]byte("cannot process request " + err.Error())) 176 | return nil, nil, err 177 | } 178 | 179 | httpRequest, err := UnpackProxyRequest(msg) 180 | if err != nil { 181 | response.Write(WriteHead(http.StatusBadRequest, Headers{})) 182 | response.Write([]byte("malformed request")) 183 | return nil, nil, err 184 | } 185 | 186 | w := &ResponseWriter{ 187 | cRes: response, 188 | req: httpRequest, 189 | handlerHeader: make(http.Header), 190 | contentLength: -1, 191 | wroteHeader: false, 192 | } 193 | 194 | return w, httpRequest, nil 195 | } 196 | 197 | // WrapHandlerFunc provides opportunity for using Go web frameworks, which supports http.HandlerFunc interface 198 | // 199 | // Trivial example is 200 | // 201 | // import ( 202 | // "net/http" 203 | // "github.com/cocaine/cocaine-framework-go/cocaine" 204 | // ) 205 | // 206 | // func handler(w http.ResponseWriter, req *http.Request) { 207 | // w.Header().Set("Content-Type", "text/plain") 208 | // w.Write([]byte("This is an example server.\n")) 209 | // } 210 | // 211 | // func main() { 212 | // binds := map[string]cocaine.EventHandler{ 213 | // "example": cocaine.WrapHandlerFunc(handler, nil), 214 | // } 215 | // if worker, err := cocaine.NewWorker(); err == nil{ 216 | // worker.Loop(binds) 217 | // }else{ 218 | // panic(err) 219 | // } 220 | // } 221 | // 222 | func WrapHandlerFunc(hf http.HandlerFunc) EventHandler { 223 | return WrapHandler(http.HandlerFunc(hf)) 224 | } 225 | 226 | // WrapHandleFuncs wraps the bunch of http.Handlers to allow them handle requests via Worker 227 | func WrapHandleFuncs(hfs map[string]http.HandlerFunc) map[string]EventHandler { 228 | handlers := make(map[string]EventHandler, len(hfs)) 229 | for key, hf := range hfs { 230 | handlers[key] = WrapHandlerFunc(hf) 231 | } 232 | 233 | return handlers 234 | } 235 | 236 | // inspired by https://github.com/golang/go/blob/master/src/net/http/transport.go#L1238 237 | // gzipReader wraps a response body so it can lazily 238 | // call gzip.NewReader on the first call to Read 239 | type gzipReader struct { 240 | body io.ReadCloser // underlying Response.Body 241 | zr io.Reader // lazily-initialized gzip reader 242 | } 243 | 244 | func (gz *gzipReader) Read(p []byte) (n int, err error) { 245 | if gz.zr == nil { 246 | gz.zr, err = gzip.NewReader(gz.body) 247 | if err != nil { 248 | return 0, err 249 | } 250 | } 251 | return gz.zr.Read(p) 252 | } 253 | func (gz *gzipReader) Close() error { 254 | return gz.body.Close() 255 | } 256 | -------------------------------------------------------------------------------- /cocaine12/protocol_test.go: -------------------------------------------------------------------------------- 1 | package cocaine12 2 | 3 | import ( 4 | "bytes" 5 | "sort" 6 | "testing" 7 | 8 | "github.com/ugorji/go/codec" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestEndpoint(t *testing.T) { 14 | e := EndpointItem{ 15 | IP: "127.0.0.1", 16 | Port: 10053, 17 | } 18 | 19 | assert.Equal(t, "127.0.0.1:10053", e.String()) 20 | } 21 | 22 | func TestAPIUnpack(t *testing.T) { 23 | // {0: ['resolve', {}, {0: ['value', {}], 1: ['error', {}]}], 24 | // 1: ['connect', {}, {0: ['write', None], 1: ['error', {}], 2: ['close', {}]}], 25 | // 2: ['refresh', {}, {0: ['value', {}], 1: ['error', {}]}], 26 | // 3: ['cluster', {}, {0: ['value', {}], 1: ['error', {}]}], 27 | // 4: ['publish', {0: ['discard', {}]}, {0: ['value', {}], 1: ['error', {}]}], 28 | // 5: ['routing', {}, {0: ['write', None], 1: ['error', {}], 2: ['close', {}]}]} 29 | payload := []byte{134, 0, 147, 167, 114, 101, 115, 111, 108, 118, 101, 128, 130, 0, 146, 165, 118, 97, 108, 117, 101, 128, 30 | 1, 146, 165, 101, 114, 114, 111, 114, 128, 1, 147, 167, 99, 111, 110, 110, 101, 99, 116, 128, 131, 0, 146, 31 | 165, 119, 114, 105, 116, 101, 192, 1, 146, 165, 101, 114, 114, 111, 114, 128, 2, 146, 165, 99, 108, 111, 115, 32 | 101, 128, 2, 147, 167, 114, 101, 102, 114, 101, 115, 104, 128, 130, 0, 146, 165, 118, 97, 108, 117, 101, 128, 1, 33 | 146, 165, 101, 114, 114, 111, 114, 128, 3, 147, 167, 99, 108, 117, 115, 116, 101, 114, 128, 130, 0, 146, 165, 118, 34 | 97, 108, 117, 101, 128, 1, 146, 165, 101, 114, 114, 111, 114, 128, 4, 147, 167, 112, 117, 98, 108, 105, 115, 104, 35 | 129, 0, 146, 167, 100, 105, 115, 99, 97, 114, 100, 128, 130, 0, 146, 165, 118, 97, 108, 117, 101, 128, 1, 146, 165, 36 | 101, 114, 114, 111, 114, 128, 5, 147, 167, 114, 111, 117, 116, 105, 110, 103, 128, 131, 0, 146, 165, 119, 114, 105, 37 | 116, 101, 192, 1, 146, 165, 101, 114, 114, 111, 114, 128, 2, 146, 165, 99, 108, 111, 115, 101, 128} 38 | 39 | var dm dispatchMap 40 | decoder := codec.NewDecoder(bytes.NewReader(payload), hAsocket) 41 | err := decoder.Decode(&dm) 42 | if !assert.NoError(t, err) { 43 | t.FailNow() 44 | } 45 | 46 | expected := []string{"resolve", "connect", "refresh", "cluster", "publish", "routing"} 47 | sort.Strings(expected) 48 | actual := dm.Methods() 49 | sort.Strings(actual) 50 | assert.Equal(t, expected, actual) 51 | 52 | for _, v := range dm { 53 | if v.Name == "connect" { 54 | assert.True(t, v.Downstream.Type() == emptyDispatch) 55 | assert.True(t, v.Upstream.Type() == otherDispatch) 56 | 57 | for _, descrItem := range *v.Upstream { 58 | t.Log(descrItem) 59 | switch descrItem.Name { 60 | case "write": 61 | assert.True(t, descrItem.Description.Type() == recursiveDispatch) 62 | case "close": 63 | assert.True(t, descrItem.Description.Type() == emptyDispatch) 64 | case "error": 65 | assert.True(t, descrItem.Description.Type() == emptyDispatch) 66 | } 67 | } 68 | } 69 | } 70 | 71 | } 72 | 73 | //'[]byte{148,1,0,147,161,65,161,66,161,67,147,147,194,80,168,124,0,0,0,0,0,0,0,147,194,81,168,159,134,1,0,0,0,0,0,82}' 74 | func TestMessageUnpack(t *testing.T) { 75 | // Payload is packed by Python: 76 | // traceid = 124 77 | // parentid = 0 78 | // spanid=99999 79 | // [(False, 80, '|\x00\x00\x00\x00\x00\x00\x00'), 80 | // (False, 81, '\x9f\x86\x01\x00\x00\x00\x00\x00'), 81 | // 82] 82 | // [1, 0, ["A", "B", "C"], z] 83 | payload := []byte{148, 100, 101, 147, 161, 65, 161, 66, 161, 67, 147, 147, 194, 80, 84 | 168, 124, 0, 0, 0, 0, 0, 0, 0, 147, 194, 81, 168, 159, 134, 1, 0, 0, 0, 0, 0, 82} 85 | decoder := codec.NewDecoder(bytes.NewReader(payload), hAsocket) 86 | 87 | var message Message 88 | decoder.MustDecode(&message) 89 | 90 | assert.Equal(t, uint64(100), message.Session) 91 | assert.Equal(t, uint64(101), message.MsgType) 92 | headers := message.Headers 93 | assert.Equal(t, 3, len(headers)) 94 | 95 | traceInfo, err := headers.getTraceData() 96 | assert.NoError(t, err) 97 | assert.Equal(t, uint64(124), traceInfo.Trace) 98 | assert.Equal(t, uint64(99999), traceInfo.Span) 99 | assert.Equal(t, uint64(0), traceInfo.Parent) 100 | } 101 | 102 | func TestMessageUnpackTraceAsString(t *testing.T) { 103 | // Payload is packed by Python: 104 | // trace_id = 124 105 | // parent_id = 124 106 | // span_id=99999 107 | // [ 108 | // (False, 'trace_id', '|\x00\x00\x00\x00\x00\x00\x00'), 109 | // (False, 'span_id', '\x9f\x86\x01\x00\x00\x00\x00\x00'), 110 | // (False, 'parent_id', '|\x00\x00\x00\x00\x00\x00\x00'), 111 | // ] 112 | // [1, 0, ["A", "B", "C"], z] 113 | payload := []byte{ 114 | 148, 100, 101, 147, 161, 65, 161, 66, 161, 67, 147, 147, 194, 168, 116, 115 | 114, 97, 99, 101, 95, 105, 100, 168, 124, 0, 0, 0, 0, 0, 0, 0, 147, 194, 116 | 167, 115, 112, 97, 110, 95, 105, 100, 168, 159, 134, 1, 0, 0, 0, 0, 0, 117 | 147, 194, 169, 112, 97, 114, 101, 110, 116, 95, 105, 100, 168, 124, 0, 0, 118 | 0, 0, 0, 0, 0, 119 | } 120 | decoder := codec.NewDecoder(bytes.NewReader(payload), hAsocket) 121 | 122 | var message Message 123 | decoder.MustDecode(&message) 124 | 125 | assert.Equal(t, uint64(100), message.Session) 126 | assert.Equal(t, uint64(101), message.MsgType) 127 | headers := message.Headers 128 | assert.Equal(t, 3, len(headers)) 129 | 130 | traceInfo, err := headers.getTraceData() 131 | assert.NoError(t, err) 132 | assert.Equal(t, uint64(124), traceInfo.Trace) 133 | assert.Equal(t, uint64(99999), traceInfo.Span) 134 | assert.Equal(t, uint64(124), traceInfo.Parent) 135 | } 136 | 137 | func TestHeaders(t *testing.T) { 138 | var ( 139 | //trace.pack_trace(trace.Trace(traceid=9000, spanid=11000, parentid=8000)) 140 | buff = []byte{ 141 | 147, 147, 194, 80, 168, 40, 35, 0, 0, 0, 0, 0, 0, 147, 194, 81, 168, 142 | 248, 42, 0, 0, 0, 0, 0, 0, 147, 194, 82, 168, 64, 31, 0, 0, 0, 0, 0, 0} 143 | headers CocaineHeaders 144 | ) 145 | codec.NewDecoderBytes(buff, hAsocket).MustDecode(&headers) 146 | 147 | assert.Equal(t, 3, len(headers)) 148 | for i, header := range headers { 149 | switch i { 150 | case 0: 151 | n, b, err := getTrace(header) 152 | assert.NoError(t, err) 153 | assert.Equal(t, uint64(traceId), n) 154 | 155 | trace, err := decodeTracingId(b) 156 | assert.NoError(t, err) 157 | assert.Equal(t, uint64(9000), trace) 158 | case 1: 159 | n, b, err := getTrace(header) 160 | assert.NoError(t, err) 161 | assert.Equal(t, uint64(spanId), n) 162 | 163 | span, err := decodeTracingId(b) 164 | assert.NoError(t, err) 165 | assert.Equal(t, uint64(11000), span) 166 | case 2: 167 | n, b, err := getTrace(header) 168 | assert.NoError(t, err) 169 | assert.Equal(t, uint64(parentId), n) 170 | 171 | parent, err := decodeTracingId(b) 172 | assert.NoError(t, err) 173 | assert.Equal(t, uint64(8000), parent) 174 | } 175 | } 176 | 177 | traceInfo, err := headers.getTraceData() 178 | assert.NoError(t, err) 179 | assert.Equal(t, uint64(9000), traceInfo.Trace) 180 | assert.Equal(t, uint64(11000), traceInfo.Span) 181 | assert.Equal(t, uint64(8000), traceInfo.Parent) 182 | } 183 | 184 | func TestHeadersPackUnpack(t *testing.T) { 185 | trace := TraceInfo{ 186 | Trace: uint64(100), 187 | Span: uint64(200), 188 | Parent: uint64(300), 189 | } 190 | 191 | headers, err := traceInfoToHeaders(&trace) 192 | assert.NoError(t, err) 193 | assert.Equal(t, 3, len(headers)) 194 | // t.Logf("%v", headers) 195 | 196 | traceInfo, err := headers.getTraceData() 197 | if !assert.NoError(t, err) { 198 | t.Logf("%v", traceInfo) 199 | t.FailNow() 200 | } 201 | 202 | assert.Equal(t, trace.Trace, traceInfo.Trace) 203 | assert.Equal(t, trace.Span, traceInfo.Span) 204 | assert.Equal(t, trace.Parent, traceInfo.Parent) 205 | } 206 | 207 | func BenchmarkTraceExtract(b *testing.B) { 208 | var ( 209 | //trace.pack_trace(trace.Trace(traceid=9000, spanid=11000, parentid=8000)) 210 | buff = []byte{ 211 | 147, 147, 194, 80, 168, 40, 35, 0, 0, 0, 0, 0, 0, 147, 194, 81, 168, 212 | 248, 42, 0, 0, 0, 0, 0, 0, 147, 194, 82, 168, 64, 31, 0, 0, 0, 0, 0, 0} 213 | headers CocaineHeaders 214 | ) 215 | codec.NewDecoderBytes(buff, hAsocket).MustDecode(&headers) 216 | b.ResetTimer() 217 | for n := 0; n < b.N; n++ { 218 | headers.getTraceData() 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /cocaine/worker.go: -------------------------------------------------------------------------------- 1 | package cocaine 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "runtime/debug" 9 | "time" 10 | 11 | uuid "github.com/satori/go.uuid" 12 | "github.com/ugorji/go/codec" 13 | ) 14 | 15 | var ( 16 | flagUUID string 17 | flagEndpoint string 18 | flagApp string 19 | flagLocator string 20 | ) 21 | 22 | func init() { 23 | flagSet := flag.NewFlagSet("cocaine11", flag.ContinueOnError) 24 | flagSet.SetOutput(ioutil.Discard) 25 | 26 | flagSet.StringVar(&flagUUID, "uuid", "", "UUID") 27 | flagSet.StringVar(&flagEndpoint, "endpoint", "", "Connection path") 28 | flagSet.StringVar(&flagApp, "app", "standalone", "Connection path") 29 | flagSet.StringVar(&flagLocator, "locator", "localhost:10053", "Connection path") 30 | flagSet.Parse(os.Args[1:]) 31 | } 32 | 33 | const ( 34 | HEARTBEAT_TIMEOUT = time.Second * 20 35 | ) 36 | 37 | type Request struct { 38 | from_worker chan []byte 39 | to_handler chan []byte 40 | quit chan bool 41 | } 42 | 43 | type EventHandler func(*Request, *Response) 44 | 45 | func newRequest() *Request { 46 | request := Request{make(chan []byte), make(chan []byte), make(chan bool)} 47 | go func() { 48 | var pending [][]byte 49 | quit := false 50 | for { 51 | var out chan []byte 52 | var first []byte 53 | if len(pending) > 0 { 54 | first = pending[0] 55 | out = request.to_handler 56 | } else { 57 | if quit { 58 | return 59 | } 60 | } 61 | select { 62 | case incoming := <-request.from_worker: 63 | pending = append(pending, incoming) 64 | case out <- first: 65 | pending[0] = nil 66 | pending = pending[1:] 67 | case <-request.quit: 68 | quit = true 69 | } 70 | } 71 | }() 72 | return &request 73 | } 74 | 75 | func (request *Request) push(data []byte) { 76 | request.from_worker <- data 77 | } 78 | 79 | func (request *Request) close() { 80 | request.quit <- true 81 | } 82 | 83 | func (request *Request) Read() chan []byte { 84 | return request.to_handler 85 | } 86 | 87 | // Datastream from worker to a client. 88 | type Response struct { 89 | session int64 90 | from_handler chan []byte 91 | to_worker chan rawMessage 92 | quit chan bool 93 | } 94 | 95 | func newResponse(session int64, to_worker chan rawMessage) *Response { 96 | response := Response{session, make(chan []byte), to_worker, make(chan bool)} 97 | go func() { 98 | var pending [][]byte 99 | quit := false 100 | for { 101 | var out chan rawMessage 102 | var first rawMessage 103 | if len(pending) > 0 { 104 | first = pending[0] 105 | out = to_worker 106 | } else { 107 | if quit { 108 | return 109 | } 110 | } 111 | select { 112 | case incoming := <-response.from_handler: 113 | pending = append(pending, incoming) 114 | case out <- first: 115 | pending[0] = nil 116 | pending = pending[1:] 117 | case quit = <-response.quit: 118 | quit = true 119 | } 120 | } 121 | }() 122 | return &response 123 | } 124 | 125 | // Sends chunk of data to a client. 126 | func (response *Response) Write(data interface{}) { 127 | var res []byte 128 | codec.NewEncoderBytes(&res, h).Encode(&data) 129 | response.from_handler <- packMsg(&chunk{messageInfo{CHUNK, response.session}, res}) 130 | } 131 | 132 | // Notify a client about finishing the datastream. 133 | func (response *Response) Close() { 134 | response.from_handler <- packMsg(&choke{messageInfo{CHOKE, response.session}}) 135 | response.quit <- true 136 | } 137 | 138 | // Send error to a client. Specify code and message, which describes this error. 139 | func (response *Response) ErrorMsg(code int, msg string) { 140 | response.from_handler <- packMsg(&errorMsg{messageInfo{ERROR, response.session}, code, msg}) 141 | } 142 | 143 | type FallbackHandler func(string, *Request, *Response) 144 | 145 | var defaultFallbackHandler = func(event string, req *Request, resp *Response) { 146 | defer resp.Close() 147 | resp.ErrorMsg(-100, fmt.Sprintf("There is no event handler for %s", event)) 148 | } 149 | 150 | // Performs IO operations between application 151 | // and cocaine-runtime, dispatches incoming messages from runtime. 152 | type Worker struct { 153 | unpacker *streamUnpacker 154 | uuid uuid.UUID 155 | logger *Logger 156 | heartbeat_timer *time.Timer 157 | disown_timer *time.Timer 158 | sessions map[int64](*Request) 159 | from_handlers chan rawMessage 160 | socketIO 161 | 162 | fallback FallbackHandler 163 | disown_timeout time.Duration 164 | } 165 | 166 | // Creates new instance of Worker. Returns error on fail. 167 | func NewWorkerWithLocalLogger(localLogger LocalLogger) (worker *Worker, err error) { 168 | sock, err := newAsyncRWSocket("unix", flagEndpoint, time.Second*5, localLogger) 169 | if err != nil { 170 | return 171 | } 172 | 173 | logger, err := NewLogger() 174 | if err != nil { 175 | return 176 | } 177 | 178 | workerID, _ := uuid.FromString(flagUUID) 179 | 180 | disown_timeout := 5 * time.Second 181 | 182 | w := Worker{ 183 | unpacker: newStreamUnpacker(), 184 | uuid: workerID, 185 | logger: logger, 186 | heartbeat_timer: time.NewTimer(HEARTBEAT_TIMEOUT), 187 | disown_timer: time.NewTimer(disown_timeout), 188 | sessions: make(map[int64](*Request)), 189 | from_handlers: make(chan rawMessage), 190 | socketIO: sock, 191 | fallback: defaultFallbackHandler, 192 | disown_timeout: disown_timeout, 193 | } 194 | w.disown_timer.Stop() 195 | w.handshake() 196 | w.heartbeat() 197 | worker = &w 198 | return 199 | } 200 | 201 | func NewWorker() (worker *Worker, err error) { 202 | return NewWorkerWithLocalLogger(&LocalLoggerImpl{}) 203 | } 204 | 205 | func (worker *Worker) SetFallbackHandler(fallback FallbackHandler) { 206 | worker.fallback = fallback 207 | } 208 | 209 | // Initializes worker in runtime as starting. Launchs an eventloop. 210 | func (worker *Worker) Loop(bind map[string]EventHandler) { 211 | for { 212 | select { 213 | case answer := <-worker.Read(): 214 | msgs := worker.unpacker.Feed(answer, worker.logger) 215 | for _, rawmsg := range msgs { 216 | switch msg := rawmsg.(type) { 217 | case *chunk: 218 | worker.logger.Debug("Receive chunk") 219 | worker.sessions[msg.getSessionID()].push(msg.Data) 220 | 221 | case *choke: 222 | worker.logger.Debug("Receive choke") 223 | worker.sessions[msg.getSessionID()].close() 224 | delete(worker.sessions, msg.getSessionID()) 225 | 226 | case *invoke: 227 | worker.logger.Debug(fmt.Sprintf("Receive invoke %s %d", msg.Event, msg.getSessionID())) 228 | cur_session := msg.getSessionID() 229 | req := newRequest() 230 | resp := newResponse(cur_session, worker.from_handlers) 231 | worker.sessions[cur_session] = req 232 | if callback, ok := bind[msg.Event]; ok { 233 | go func() { 234 | defer func() { 235 | if r := recover(); r != nil { 236 | errMsg := fmt.Sprintf("Error in event: '%s', exception: %s", msg.Event, r) 237 | worker.logger.Err(fmt.Sprintf("%s \n Stacktrace: \n %s", 238 | errMsg, string(debug.Stack()))) 239 | resp.ErrorMsg(1, errMsg) 240 | resp.Close() 241 | } 242 | }() 243 | callback(req, resp) 244 | }() 245 | } else { 246 | go func() { 247 | defer func() { 248 | if r := recover(); r != nil { 249 | resp.Close() 250 | } 251 | }() 252 | worker.fallback(msg.Event, req, resp) 253 | }() 254 | } 255 | 256 | case *heartbeat: 257 | worker.logger.Debug("Receive heartbeat. Stop disown_timer") 258 | worker.disown_timer.Stop() 259 | 260 | case *terminateStruct: 261 | worker.logger.Info("Receive terminate") 262 | os.Exit(0) 263 | 264 | default: 265 | worker.logger.Warn("Unknown message") 266 | } 267 | } 268 | case <-worker.heartbeat_timer.C: 269 | worker.logger.Debug("Send heartbeat") 270 | worker.heartbeat() 271 | 272 | case <-worker.disown_timer.C: 273 | worker.logger.Info("Disowned") 274 | os.Exit(0) 275 | 276 | case outcoming := <-worker.from_handlers: 277 | worker.Write() <- outcoming 278 | } 279 | } 280 | } 281 | 282 | func (worker *Worker) heartbeat() { 283 | heartbeat := heartbeat{messageInfo{HEARTBEAT, 0}} 284 | worker.Write() <- packMsg(&heartbeat) 285 | worker.disown_timer.Reset(worker.disown_timeout) 286 | worker.heartbeat_timer.Reset(HEARTBEAT_TIMEOUT) 287 | } 288 | 289 | func (worker *Worker) handshake() { 290 | handshake := handshakeStruct{messageInfo{HANDSHAKE, 0}, worker.uuid} 291 | worker.Write() <- packMsg(&handshake) 292 | } 293 | 294 | func (worker *Worker) SetDisownTimeout(timeout time.Duration) { 295 | worker.disown_timeout = timeout 296 | } 297 | --------------------------------------------------------------------------------