cp -r vendor/github.com/golang/net vendor/golang.org/x/net
cp -r vendor/github.com/golang/text vendor/golang.org/x/text
cp -r vendor/github.com/golang/tools vendor/golang.org/x/tools
+ cp -r vendor/github.com/golang/time vendor/golang.org/x/time
# dist builds binaries for all platforms and packages them for distribution
dist:
"errors"
"reflect"
"time"
+ "net/http"
wire "github.com/tendermint/go-wire"
"github.com/bytom/p2p"
cmn "github.com/tendermint/tmlibs/common"
"github.com/bytom/blockchain/txdb"
"github.com/bytom/blockchain/account"
+ //"github.com/bytom/net/http/gzip"
+ //"github.com/bytom/net/http/httpjson"
+ //"github.com/bytom/net/http/limit"
+ //"github.com/bytom/net/http/static"
)
const (
store *txdb.Store
accounts *account.Manager
pool *BlockPool
+ mux *http.ServeMux
fastSync bool
requestsCh chan BlockRequest
timeoutsCh chan string
- name: github.com/golang/net
- name: github.com/golang/text
- name: github.com/golang/tools
+- name: github.com/golang/time
- name: golang.org/x/sys
version: e62c3de784db939836898e5c19ffd41bece347da
subpackages:
--- /dev/null
+package gzip
+
+import (
+ "bufio"
+ "compress/gzip"
+ "errors"
+ "io"
+ "net"
+ "net/http"
+ "strings"
+ "sync"
+)
+
+var pool = sync.Pool{
+ New: func() interface{} {
+ w, _ := gzip.NewWriterLevel(nil, gzip.BestSpeed) // #nosec
+ return w
+ },
+}
+
+func getWriter(w io.Writer) *gzip.Writer {
+ gz := pool.Get().(*gzip.Writer)
+ gz.Reset(w)
+ return gz
+}
+
+type Handler struct {
+ Handler http.Handler
+}
+
+func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Vary", "Accept-Encoding")
+ if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
+ h.Handler.ServeHTTP(w, r)
+ return
+ }
+ w.Header().Set("Content-Encoding", "gzip")
+ gz := getWriter(w)
+ w = &responseWriter{gz, w}
+ h.Handler.ServeHTTP(w, r)
+ gz.Close()
+ pool.Put(gz)
+}
+
+type responseWriter struct {
+ w io.Writer // w wraps only method Write
+ http.ResponseWriter // embedded for the other methods
+}
+
+var _ http.ResponseWriter = (*responseWriter)(nil)
+var _ http.Hijacker = (*responseWriter)(nil)
+
+func (w *responseWriter) Write(p []byte) (int, error) { return w.w.Write(p) }
+
+func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ h, ok := w.ResponseWriter.(http.Hijacker)
+ if !ok {
+ return nil, nil, errors.New("not a hijacker")
+ }
+ return h.Hijack()
+}
--- /dev/null
+package gzip
+
+import (
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+var (
+ small = []byte(`{"message":"ok"}`)
+ medium = []byte(`{"id":"961458e16018cb60f06b01d303ae0d8e2b3ff98698a1f80b5c6715969644f519","timestamp":"2016-10-04T19:13:23Z","block_id":"181c11b24c7dbdd5ce5e2b9da1b665878a80712dbfd1796613e66305da49ca7c","block_height":2,"position":0,"reference_data":{},"is_local":"yes","inputs":[{"action":"issue","asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":5,"issuance_program":"027b7d75766baa205fd08ca9e18b180c7da3ace70e890cba8c7014c7da5c9ed78e3d9e253cccec8f5151ad696c00c0","reference_data":{},"is_local":"yes"}],"outputs":[{"action":"control","purpose":"receive","position":0,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":5,"account_id":"acc0KNY9W8QG0802","account_alias":"t","account_tags":null,"control_program":"766baa20107c8767129a4ae5325371946e44f4ae76448452f722768048bd2d5cf12fc1595151ad696c00c0","reference_data":{},"is_local":"yes"}]}`)
+ large = []byte(`{"items":[{"id":"0266cf2ed4ff3cb989341a9f9b2c3e7ffcdf2133ee84df9652834ca97a9bfe53","timestamp":"2016-10-04T20:31:02Z","block_id":"c77d0004600ce1de5ac1c815dba6fd3512b396292203e96b56c3e618a8c07113","block_height":8,"position":0,"reference_data":{},"is_local":"yes","inputs":[{"action":"spend","asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":20,"spent_output":{"position":1,"transaction_id":"092de8cc56abcd588919973c2eb5b5a56355e4d4d50910f5bcfa25ca4e2c0124"},"account_id":"acc0KP0F1K9G081A","account_alias":"foo","account_tags":null,"reference_data":{},"is_local":"yes"}],"outputs":[{"action":"control","purpose":"change","position":0,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":17,"account_id":"acc0KP0F1K9G081A","account_alias":"foo","account_tags":null,"control_program":"766baa2097abd5c16fab864da84605e5cc2ae96e946949c63470658f3b34a10e8594bc485151ad696c00c0","reference_data":{},"is_local":"yes"},{"action":"retire","position":1,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":3,"control_program":"6a","reference_data":{},"is_local":"no"}]},{"id":"712e79e954150750db4e245112429e18de7e67465858074ef79b5a83621d52f7","timestamp":"2016-10-04T20:30:37Z","block_id":"9a2e4a3f8a837935ef7c1d40e0032922fcdbf1e5e152222e7f2b9b5695170b03","block_height":7,"position":0,"reference_data":{},"is_local":"yes","inputs":[{"action":"issue","asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":20,"issuance_program":"027b7d75766baa205fd08ca9e18b180c7da3ace70e890cba8c7014c7da5c9ed78e3d9e253cccec8f5151ad696c00c0","reference_data":{},"is_local":"yes"}],"outputs":[{"action":"retire","position":0,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":20,"control_program":"6a","reference_data":{},"is_local":"no"}]},{"id":"092de8cc56abcd588919973c2eb5b5a56355e4d4d50910f5bcfa25ca4e2c0124","timestamp":"2016-10-04T20:30:13Z","block_id":"bf38fe464a50a24e90ecf01b75101c7063c94c7ac6e4ec9d349ced0033c6b233","block_height":6,"position":0,"reference_data":{},"is_local":"yes","inputs":[{"action":"spend","asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":55,"spent_output":{"position":0,"transaction_id":"8c851f25563a33b79a3f30139c88854c607979db437f71b31549c664f6995113"},"account_id":"acc0KNY9W8QG0802","account_alias":"t","account_tags":null,"reference_data":{},"is_local":"yes"}],"outputs":[{"action":"control","purpose":"change","position":0,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":35,"account_id":"acc0KNY9W8QG0802","account_alias":"t","account_tags":null,"control_program":"766baa20ae40f6e7509b6a86deab669f36b3a8bd43a4d6128904e97ebabecb81473084a65151ad696c00c0","reference_data":{},"is_local":"yes"},{"action":"control","purpose":"receive","position":1,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":20,"account_id":"acc0KP0F1K9G081A","account_alias":"foo","account_tags":null,"control_program":"766baa20a2abff2f62e5a9912bc9776c170f66219077adab278ca683b4583b7a9d6c83b85151ad696c00c0","reference_data":{},"is_local":"yes"}]},{"id":"4e6ad8970866d652f755c32fd04868ecd63d292021b3961ec4a87d9d8cd97ccc","timestamp":"2016-10-04T20:29:26Z","block_id":"033e802edc6d4b22c17679a0a1512dc04448b13dc2b2194c58ec11ac4c4c4cab","block_height":5,"position":0,"reference_data":{},"is_local":"yes","inputs":[{"action":"issue","asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":1,"issuance_program":"027b7d75766baa205fd08ca9e18b180c7da3ace70e890cba8c7014c7da5c9ed78e3d9e253cccec8f5151ad696c00c0","reference_data":{},"is_local":"yes"}],"outputs":[{"action":"control","purpose":"receive","position":0,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":1,"account_id":"acc0KP0F1K9G081A","account_alias":"foo","account_tags":null,"control_program":"766baa205dfbb62393e5e6d3b2a0772dd8fc1f865a0c774c44ee7de1bb40b18de5368bf55151ad696c00c0","reference_data":{},"is_local":"yes"}]},{"id":"8599e2feb681b84ae9e8233183c73b8a5bbf7564a9f7061b926f6b7040824608","timestamp":"2016-10-04T20:28:42Z","block_id":"cb6c384181883422e42f87637373593f7821df24723b89fb77f10fef69a73235","block_height":4,"position":0,"reference_data":{},"is_local":"yes","inputs":[{"action":"spend","asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":44,"spent_output":{"position":1,"transaction_id":"8c851f25563a33b79a3f30139c88854c607979db437f71b31549c664f6995113"},"account_id":"acc0KNY9W8QG0802","account_alias":"t","account_tags":null,"reference_data":{},"is_local":"yes"}],"outputs":[{"action":"control","purpose":"change","position":0,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":39,"account_id":"acc0KNY9W8QG0802","account_alias":"t","account_tags":null,"control_program":"766baa20d16227a885f913a958ff7e9df91118874133aba484175da7fb5e24b4bf6710315151ad696c00c0","reference_data":{},"is_local":"yes"},{"action":"control","purpose":"receive","position":1,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":5,"account_id":"acc0KP0F1K9G081A","account_alias":"foo","account_tags":null,"control_program":"766baa20153e9de41ba01abc10d5e7dbe5c3c222733c653cf8520a984802e7eaf18ba7855151ad696c00c0","reference_data":{},"is_local":"yes"}]},{"id":"8c851f25563a33b79a3f30139c88854c607979db437f71b31549c664f6995113","timestamp":"2016-10-04T20:23:27Z","block_id":"eb2593b3a13b386eab0dcc9f4c8aa5d03e01696d866e06c9048b7786127c163a","block_height":3,"position":0,"reference_data":{},"is_local":"yes","inputs":[{"action":"issue","asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":99,"issuance_program":"027b7d75766baa205fd08ca9e18b180c7da3ace70e890cba8c7014c7da5c9ed78e3d9e253cccec8f5151ad696c00c0","reference_data":{},"is_local":"yes"}],"outputs":[{"action":"control","purpose":"receive","position":0,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":55,"account_id":"acc0KNY9W8QG0802","account_alias":"t","account_tags":null,"control_program":"766baa209997e49055a4e9b020c3c2342a632b0977f8020778d3607acceacd5f0f8fc7fe5151ad696c00c0","reference_data":{},"is_local":"yes"},{"action":"control","purpose":"receive","position":1,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":44,"account_id":"acc0KNY9W8QG0802","account_alias":"t","account_tags":null,"control_program":"766baa20da29a78752723e4c873e1c46eafc0dfd22041fe2e818ed5584c9b3139fc5363a5151ad696c00c0","reference_data":{},"is_local":"yes"}]},{"id":"961458e16018cb60f06b01d303ae0d8e2b3ff98698a1f80b5c6715969644f519","timestamp":"2016-10-04T19:13:23Z","block_id":"181c11b24c7dbdd5ce5e2b9da1b665878a80712dbfd1796613e66305da49ca7c","block_height":2,"position":0,"reference_data":{},"is_local":"yes","inputs":[{"action":"issue","asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":5,"issuance_program":"027b7d75766baa205fd08ca9e18b180c7da3ace70e890cba8c7014c7da5c9ed78e3d9e253cccec8f5151ad696c00c0","reference_data":{},"is_local":"yes"}],"outputs":[{"action":"control","purpose":"receive","position":0,"asset_id":"1811eb7d8aebbfc39ea14a2da0ae840e9b447952d6e205756ea5c7ad028bcc97","asset_alias":"t","asset_definition":{},"asset_tags":{},"asset_is_local":"yes","amount":5,"account_id":"acc0KNY9W8QG0802","account_alias":"t","account_tags":null,"control_program":"766baa20107c8767129a4ae5325371946e44f4ae76448452f722768048bd2d5cf12fc1595151ad696c00c0","reference_data":{},"is_local":"yes"}]}],"next":{"page_size":0,"timeout":0,"after":"2:0-1","end_time":1475613062958,"type":""},"last_page":true}`)
+)
+
+type noOpWriter struct{ header http.Header }
+
+func (n noOpWriter) Header() http.Header {
+ return n.header
+}
+
+func (n noOpWriter) Write(d []byte) (int, error) {
+ return len(d), nil
+}
+
+func (n noOpWriter) WriteHeader(int) {}
+
+func BenchmarkGzipSmall(b *testing.B) {
+ r, _ := http.NewRequest("GET", "/foo", nil) // #nosec
+ r.Header.Set("accept-encoding", "gzip")
+ h := Handler{http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write(small)
+ })}
+ w := noOpWriter{header: http.Header{}}
+
+ for i := 0; i < b.N; i++ {
+ h.ServeHTTP(&w, r)
+ }
+ b.SetBytes(int64(len(small)))
+}
+
+func BenchmarkGzipMedium(b *testing.B) {
+ r, _ := http.NewRequest("GET", "/foo", nil)
+ r.Header.Set("accept-encoding", "gzip")
+ h := Handler{http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write(medium)
+ })}
+ w := noOpWriter{header: http.Header{}}
+
+ for i := 0; i < b.N; i++ {
+ h.ServeHTTP(&w, r)
+ }
+ b.SetBytes(int64(len(medium)))
+}
+
+func BenchmarkGzipLarge(b *testing.B) {
+ r, _ := http.NewRequest("GET", "/foo", nil)
+ r.Header.Set("accept-encoding", "gzip")
+ h := Handler{http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write(large)
+ })}
+ w := noOpWriter{header: http.Header{}}
+
+ for i := 0; i < b.N; i++ {
+ h.ServeHTTP(&w, r)
+ }
+ b.SetBytes(int64(len(large)))
+}
+
+func TestGzip(t *testing.T) {
+ w := httptest.NewRecorder()
+ r, _ := http.NewRequest("GET", "/foo", nil)
+ r.Header.Set("accept-encoding", "gzip")
+ h := Handler{http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, "hello, world")
+ })}
+ h.ServeHTTP(w, r)
+ if s := w.HeaderMap.Get("content-encoding"); s != "gzip" {
+ t.Errorf(`w.HeaderMap.Get("content-encoding") = %s want gzip`, s)
+ }
+}
+
+func TestNoGzip(t *testing.T) {
+ w := httptest.NewRecorder()
+ r, _ := http.NewRequest("GET", "/foo", nil)
+ h := Handler{http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, "hello, world")
+ })}
+ h.ServeHTTP(w, r)
+ if w.HeaderMap.Get("content-encoding") == "gzip" {
+ t.Error("unexpected gzip")
+ }
+}
--- /dev/null
+// Package httperror defines the format for HTTP error responses
+// from Chain services.
+package httperror
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/bytom/errors"
+ "github.com/bytom/log"
+ "github.com/bytom/net/http/httpjson"
+ "github.com/bytom/net/http/reqid"
+)
+
+func init() {
+ log.SkipFunc("chain/net/http/httperror.Formatter.Log")
+ log.SkipFunc("chain/net/http/httperror.Formatter.Write")
+}
+
+// Info contains a set of error codes to send to the user.
+type Info struct {
+ HTTPStatus int `json:"-"`
+ ChainCode string `json:"code"`
+ Message string `json:"message"`
+}
+
+// Response defines the error response for a Chain error.
+type Response struct {
+ Info
+ Detail string `json:"detail,omitempty"`
+ Data map[string]interface{} `json:"data,omitempty"`
+ Temporary bool `json:"temporary"`
+}
+
+// Formatter defines rules for mapping errors to the Chain error
+// response format.
+type Formatter struct {
+ Default Info
+ IsTemporary func(info Info, err error) bool
+ Errors map[error]Info
+}
+
+// Format builds an error Response body describing err by consulting
+// the f.Errors lookup table. If no entry is found, it returns f.Default.
+func (f Formatter) Format(err error) (body Response) {
+ root := errors.Root(err)
+ // Some types cannot be used as map keys, for example slices.
+ // If an error's underlying type is one of these, don't panic.
+ // Just treat it like any other missing entry.
+ defer func() {
+ if err := recover(); err != nil {
+ body = Response{f.Default, "", nil, true}
+ }
+ }()
+ info, ok := f.Errors[root]
+ if !ok {
+ info = f.Default
+ }
+
+ body = Response{
+ Info: info,
+ Detail: errors.Detail(err),
+ Data: errors.Data(err),
+ Temporary: f.IsTemporary(info, err),
+ }
+ return body
+}
+
+// Write writes a json encoded Response to the ResponseWriter.
+// It uses the status code associated with the error.
+//
+// Write may be used as an ErrorWriter in the httpjson package.
+func (f Formatter) Write(ctx context.Context, w http.ResponseWriter, err error) {
+ f.Log(ctx, err)
+ resp := f.Format(err)
+ httpjson.Write(ctx, w, resp.HTTPStatus, resp)
+}
+
+// Log writes a structured log entry to the chain/log logger with
+// information about the error and the HTTP response.
+func (f Formatter) Log(ctx context.Context, err error) {
+ var errorMessage string
+ if err != nil {
+ // strip the stack trace, if there is one
+ errorMessage = err.Error()
+ }
+
+ resp := f.Format(err)
+ keyvals := []interface{}{
+ "status", resp.HTTPStatus,
+ "chaincode", resp.ChainCode,
+ "path", reqid.PathFromContext(ctx),
+ log.KeyError, errorMessage,
+ }
+ if resp.HTTPStatus == 500 {
+ keyvals = append(keyvals, log.KeyStack, errors.Stack(err))
+ }
+ log.Printkv(ctx, keyvals...)
+}
--- /dev/null
+package httperror
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/bytom/errors"
+ "github.com/bytom/log"
+)
+
+var (
+ errNotFound = errors.New("not found")
+ testFormatter = Formatter{
+ Default: Info{500, "CH000", "Internal server error"},
+ IsTemporary: func(Info, error) bool { return false },
+ Errors: map[error]Info{
+ errNotFound: {400, "CH002", "Not found"},
+ },
+ }
+)
+
+// Dummy error type, to test that Format
+// doesn't panic when it's used as a map key.
+type sliceError []int
+
+func (err sliceError) Error() string { return "slice error" }
+
+func TestInfo(t *testing.T) {
+ cases := []struct {
+ err error
+ want int
+ }{
+ {nil, 500},
+ {context.Canceled, 500},
+ {errNotFound, 400},
+ {errors.Wrap(errNotFound, "foo"), 400},
+ {sliceError{}, 500},
+ {fmt.Errorf("an error!"), 500},
+ }
+
+ for _, test := range cases {
+ resp := testFormatter.Format(test.err)
+ got := resp.HTTPStatus
+ if got != test.want {
+ t.Errorf("errInfo(%#v) = %d want %d", test.err, got, test.want)
+ }
+ }
+}
+
+func TestLogSkip(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+
+ formatter := Formatter{
+ Default: Info{500, "CH000", "Internal server error"},
+ IsTemporary: func(Info, error) bool { return false },
+ Errors: map[error]Info{},
+ }
+ formatter.Log(context.Background(), errors.New("an unmapped error"))
+
+ logStr := string(buf.Bytes())
+ if len(logStr) == 0 {
+ t.Error("expected error to be logged")
+ }
+ if strings.Contains(logStr, "at=httperror.go") {
+ t.Errorf("expected httperror stack frames to be skipped but got:\n%s", logStr)
+ }
+ if !strings.Contains(logStr, "status=500") {
+ t.Errorf("expected status code of default error info but got:\n%s", logStr)
+ }
+ t.Log(logStr)
+}
--- /dev/null
+package httpjson
+
+import (
+ "context"
+ "net/http"
+)
+
+// key is an unexported type for keys defined in this package.
+// This prevents collisions with keys defined in other packages.
+type key int
+
+// Keys for HTTP objects in Contexts.
+// They are unexported; clients use Request and ResponseWriter
+// instead of using these keys directly.
+const (
+ reqKey key = iota
+ respKey
+)
+
+// Request returns the HTTP request stored in ctx.
+// If there is none, it panics.
+// The context given to a handler function
+// registered in this package is guaranteed to have
+// a request.
+func Request(ctx context.Context) *http.Request {
+ return ctx.Value(reqKey).(*http.Request)
+}
+
+// ResponseWriter returns the HTTP response writer stored in ctx.
+// If there is none, it panics.
+// The context given to a handler function
+// registered in this package is guaranteed to have
+// a response writer.
+func ResponseWriter(ctx context.Context) http.ResponseWriter {
+ return ctx.Value(respKey).(http.ResponseWriter)
+}
+
+// WithRequest returns a context with an HTTP request stored in it.
+// It is useful for testing.
+func WithRequest(ctx context.Context, req *http.Request) context.Context {
+ return context.WithValue(ctx, reqKey, req)
+}
--- /dev/null
+package httpjson
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+func TestContext(t *testing.T) {
+ wantHead := "bar"
+ wantRespHead := "baz"
+ f := func(ctx context.Context) {
+ if g := Request(ctx).Header.Get("Test-Key"); g != wantHead {
+ t.Errorf("header = %q want %q", g, wantHead)
+ }
+ ResponseWriter(ctx).Header().Set("Test-Resp-Key", wantRespHead)
+ }
+
+ h, err := Handler(f, nil)
+ if err != nil {
+ t.Fatalf("err = %v", err)
+ }
+
+ resp := httptest.NewRecorder()
+ req, _ := http.NewRequest("GET", "/", nil)
+ req.Header.Set("Test-Key", wantHead)
+ h.ServeHTTP(resp, req)
+ if g := resp.Header().Get("Test-Resp-Key"); g != wantRespHead {
+ t.Errorf("header = %q want %q", g, wantRespHead)
+ }
+}
--- /dev/null
+/*
+
+Package httpjson creates HTTP handlers to map request
+and response formats onto Go function signatures.
+The request body is decoded as a JSON text
+into an arbitrary value and the function's return value
+is encoded as a JSON text for the response body.
+The function's signature determines the types of the
+input and output values.
+
+For example, the handler for a function with signature
+
+ func(struct{ FavColor, Birthday string })
+
+would read the JSON request body into a variable
+of type struct{ FavColor, Birthday string }, then call
+the function.
+
+The allowed elements of a function signature are:
+
+ parameters:
+ Context (optional)
+ request body (optional)
+
+ return values:
+ response body (optional)
+ error (optional)
+
+All elements are optional.
+Thus, the smallest possible function signature is
+
+ func()
+
+If the function returns a non-nil error,
+the handler will call the error function provided
+in its constructor.
+Otherwise, the handler will write the return value
+as JSON text to the response body.
+If the return type is omitted, the handler will send
+a default response value.
+
+*/
+package httpjson
--- /dev/null
+package httpjson
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "net/http"
+ "reflect"
+)
+
+// ErrorWriter is responsible for writing the provided error value
+// to the response.
+type ErrorWriter func(context.Context, http.ResponseWriter, error)
+
+// DefaultResponse will be sent as the response body
+// when the handler function signature
+// has no return value.
+var DefaultResponse = json.RawMessage(`{"message":"ok"}`)
+
+// handler is an http.Handler that calls a function for each request.
+// It uses the signature of the function to decide how to interpret
+type handler struct {
+ fv reflect.Value
+ inType reflect.Type
+ hasCtx bool
+ errFunc ErrorWriter
+}
+
+// Handler returns an HTTP handler for function f.
+// See the package doc for details on allowed signatures for f.
+// If f returns a non-nil error, the handler will call errFunc.
+func Handler(f interface{}, errFunc ErrorWriter) (http.Handler, error) {
+ fv := reflect.ValueOf(f)
+ hasCtx, inType, err := funcInputType(fv)
+ if err != nil {
+ return nil, err
+ }
+
+ h := &handler{fv, inType, hasCtx, errFunc}
+ return h, nil
+}
+
+func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ var a []reflect.Value
+ if h.hasCtx {
+ ctx := req.Context()
+ ctx = context.WithValue(ctx, reqKey, req)
+ ctx = context.WithValue(ctx, respKey, w)
+ a = append(a, reflect.ValueOf(ctx))
+ }
+ if h.inType != nil {
+ inPtr := reflect.New(h.inType)
+ err := Read(req.Context(), req.Body, inPtr.Interface())
+ if err != nil {
+ h.errFunc(req.Context(), w, err)
+ return
+ }
+ a = append(a, inPtr.Elem())
+ }
+ rv := h.fv.Call(a)
+
+ var (
+ res interface{}
+ err error
+ )
+ switch n := len(rv); {
+ case n == 0:
+ res = &DefaultResponse
+ case n == 1 && !h.fv.Type().Out(0).Implements(errorType):
+ res = rv[0].Interface()
+ case n == 1 && h.fv.Type().Out(0).Implements(errorType):
+ // out param is of type error; its value can still be nil
+ res = &DefaultResponse
+ err, _ = rv[0].Interface().(error)
+ case n == 2:
+ res = rv[0].Interface()
+ err, _ = rv[1].Interface().(error)
+ }
+ if err != nil {
+ h.errFunc(req.Context(), w, err)
+ return
+ }
+
+ Write(req.Context(), w, 200, res)
+}
+
+var (
+ errorType = reflect.TypeOf((*error)(nil)).Elem()
+ contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
+)
+
+func funcInputType(fv reflect.Value) (hasCtx bool, t reflect.Type, err error) {
+ ft := fv.Type()
+ if ft.Kind() != reflect.Func || ft.IsVariadic() {
+ return false, nil, errors.New("need nonvariadic func in " + ft.String())
+ }
+
+ off := 0 // or 1 with context
+ hasCtx = ft.NumIn() >= 1 && ft.In(0).Implements(contextType)
+ if hasCtx {
+ off = 1
+ }
+
+ if ft.NumIn() > off+1 {
+ return false, nil, errors.New("too many params in " + ft.String())
+ }
+
+ if ft.NumIn() == off+1 {
+ t = ft.In(ft.NumIn() - 1)
+ }
+
+ if n := ft.NumOut(); n == 2 && !ft.Out(1).Implements(errorType) {
+ return false, nil, errors.New("second return value must be error in " + ft.String())
+ } else if n > 2 {
+ return false, nil, errors.New("need at most two return values in " + ft.String())
+ }
+
+ return hasCtx, t, nil
+}
--- /dev/null
+package httpjson
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "strings"
+ "testing"
+ "testing/iotest"
+
+ "github.com/bytom/errors"
+)
+
+func TestHandler(t *testing.T) {
+ errX := errors.New("x")
+
+ cases := []struct {
+ rawQuery string
+ input string
+ output string
+ f interface{}
+ wantErr error
+ }{
+ {"", ``, `{"message":"ok"}`, func() {}, nil},
+ {"", ``, `1`, func() int { return 1 }, nil},
+ {"", ``, `{"message":"ok"}`, func() error { return nil }, nil},
+ {"", ``, ``, func() error { return errX }, errX},
+ {"", ``, `1`, func() (int, error) { return 1, nil }, nil},
+ {"", ``, ``, func() (int, error) { return 0, errX }, errX},
+ {"", `1`, `1`, func(i int) int { return i }, nil},
+ {"", `1`, `1`, func(i *int) int { return *i }, nil},
+ {"", `"foo"`, `"foo"`, func(s string) string { return s }, nil},
+ {"", `{"x":1}`, `1`, func(x struct{ X int }) int { return x.X }, nil},
+ {"", `{"x":1}`, `1`, func(x *struct{ X int }) int { return x.X }, nil},
+ {"", ``, `1`, func(ctx context.Context) int { return ctx.Value("k").(int) }, nil},
+ }
+
+ for _, test := range cases {
+ var gotErr error
+ errFunc := func(ctx context.Context, w http.ResponseWriter, err error) {
+ gotErr = err
+ }
+ h, err := Handler(test.f, errFunc)
+ if err != nil {
+ t.Errorf("Handler(%v) got err %v", test.f, err)
+ continue
+ }
+
+ resp := httptest.NewRecorder()
+ req, _ := http.NewRequest("GET", "/", strings.NewReader(test.input))
+ req.URL.RawQuery = test.rawQuery
+ ctx := context.WithValue(context.Background(), "k", 1)
+ h.ServeHTTP(resp, req.WithContext(ctx))
+ if resp.Code != 200 {
+ t.Errorf("%T response code = %d want 200", test.f, resp.Code)
+ }
+ got := strings.TrimSpace(resp.Body.String())
+ if got != test.output {
+ t.Errorf("%T response body = %#q want %#q", test.f, got, test.output)
+ }
+ if gotErr != test.wantErr {
+ t.Errorf("%T err = %v want %v", test.f, gotErr, test.wantErr)
+ }
+ }
+}
+
+func TestReadErr(t *testing.T) {
+ var gotErr error
+ errFunc := func(ctx context.Context, w http.ResponseWriter, err error) {
+ gotErr = errors.Root(err)
+ }
+ h, _ := Handler(func(int) {}, errFunc)
+
+ resp := httptest.NewRecorder()
+ body := iotest.OneByteReader(iotest.TimeoutReader(strings.NewReader("123456")))
+ req, _ := http.NewRequest("GET", "/", body)
+ h.ServeHTTP(resp, req)
+ if got := resp.Body.Len(); got != 0 {
+ t.Errorf("len(response) = %d want 0", got)
+ }
+ wantErr := ErrBadRequest
+ if gotErr != wantErr {
+ t.Errorf("err = %v want %v", gotErr, wantErr)
+ }
+}
+
+func TestFuncInputTypeError(t *testing.T) {
+ cases := []interface{}{
+ 0,
+ "foo",
+ func() (int, int) { return 0, 0 },
+ func(string, int) {},
+ func() (int, int, error) { return 0, 0, nil },
+ }
+
+ for _, testf := range cases {
+ _, _, err := funcInputType(reflect.ValueOf(testf))
+ if err == nil {
+ t.Errorf("funcInputType(%T) want error", testf)
+ }
+
+ _, err = Handler(testf, nil)
+ if err == nil {
+ t.Errorf("funcInputType(%T) want error", testf)
+ }
+ }
+}
+
+var (
+ intType = reflect.TypeOf(0)
+ intpType = reflect.TypeOf((*int)(nil))
+ stringType = reflect.TypeOf("")
+)
+
+func TestFuncInputTypeOk(t *testing.T) {
+ cases := []struct {
+ f interface{}
+ wantCtx bool
+ wantT reflect.Type
+ }{
+ {func() {}, false, nil},
+ {func() int { return 0 }, false, nil},
+ {func() error { return nil }, false, nil},
+ {func() (int, error) { return 0, nil }, false, nil},
+ {func(int) {}, false, intType},
+ {func(*int) {}, false, intpType},
+ {func(context.Context) {}, true, nil},
+ {func(string) {}, false, stringType}, // req body is string
+ }
+
+ for _, test := range cases {
+ gotCtx, gotT, err := funcInputType(reflect.ValueOf(test.f))
+ if err != nil {
+ t.Errorf("funcInputType(%T) got error: %v", test.f, err)
+ }
+ if gotCtx != test.wantCtx {
+ t.Errorf("funcInputType(%T) context = %v want %v", test.f, gotCtx, test.wantCtx)
+ }
+ if gotT != test.wantT {
+ t.Errorf("funcInputType(%T) = %v want %v", test.f, gotT, test.wantT)
+ }
+ }
+}
--- /dev/null
+package httpjson
+
+import (
+ "context"
+ "encoding/json"
+ "io"
+ "net/http"
+ "reflect"
+
+ "github.com/bytom/errors"
+ "github.com/bytom/log"
+)
+
+// ErrBadRequest indicates the user supplied malformed JSON input,
+// possibly including a datatype that doesn't match what we expected.
+var ErrBadRequest = errors.New("httpjson: bad request")
+
+// Read decodes a single JSON text from r into v.
+// The only error it returns is ErrBadRequest
+// (wrapped with the original error message as context).
+func Read(ctx context.Context, r io.Reader, v interface{}) error {
+ dec := json.NewDecoder(r)
+ dec.UseNumber()
+ err := dec.Decode(v)
+ if err != nil {
+ detail := errors.Detail(err)
+ if detail == "" {
+ detail = "check request parameters for missing and/or incorrect values"
+ }
+ return errors.WithDetail(ErrBadRequest, err.Error()+": "+detail)
+ }
+ return err
+}
+
+// Write sets the Content-Type header field to indicate
+// JSON data, writes the header using status,
+// then writes v to w.
+// It logs any error encountered during the write.
+func Write(ctx context.Context, w http.ResponseWriter, status int, v interface{}) {
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ w.WriteHeader(status)
+
+ err := json.NewEncoder(w).Encode(Array(v))
+ if err != nil {
+ log.Error(ctx, err)
+ }
+}
+
+// Array returns an empty JSON array if v is a nil slice,
+// so that it renders as "[]" rather than "null".
+// Otherwise, it returns v.
+func Array(v interface{}) interface{} {
+ if rv := reflect.ValueOf(v); rv.Kind() == reflect.Slice && rv.IsNil() {
+ v = []struct{}{}
+ }
+ return v
+}
--- /dev/null
+package httpjson
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "net/http/httptest"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/bytom/log"
+)
+
+func TestWriteArray(t *testing.T) {
+ examples := []struct {
+ in []int
+ want string
+ }{
+ {nil, "[]"},
+ {[]int{}, "[]"},
+ {make([]int, 0), "[]"},
+ }
+
+ for _, ex := range examples {
+ rec := httptest.NewRecorder()
+ Write(context.Background(), rec, 200, ex.in)
+ got := strings.TrimSpace(rec.Body.String())
+ if got != ex.want {
+ t.Errorf("Write(%v) = %v want %v", ex.in, got, ex.want)
+ }
+ }
+}
+
+func TestWriteErr(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+ defer log.SetOutput(os.Stderr)
+
+ want := "test-error"
+
+ ctx := context.Background()
+ resp := &errResponse{httptest.NewRecorder(), errors.New(want)}
+ Write(ctx, resp, 200, "ok")
+ got := buf.String()
+ if !strings.Contains(got, want) {
+ t.Errorf("log = %v; should contain %q", got, want)
+ }
+}
+
+type errResponse struct {
+ *httptest.ResponseRecorder
+ err error
+}
+
+func (r *errResponse) Write([]byte) (int, error) {
+ return 0, r.err
+}
--- /dev/null
+package limit
+
+import (
+ "net/http"
+ "sync"
+
+ "golang.org/x/time/rate"
+)
+
+type BucketLimiter struct {
+ freq rate.Limit
+ burst int
+
+ bucketMu sync.Mutex // protects the following
+ buckets map[string]*rate.Limiter
+}
+
+func NewBucketLimiter(freq, burst int) *BucketLimiter {
+ return &BucketLimiter{
+ freq: rate.Limit(freq),
+ burst: burst,
+ buckets: make(map[string]*rate.Limiter),
+ }
+}
+
+func (b *BucketLimiter) Allow(id string) bool {
+ return b.bucket(id).Allow()
+}
+
+func (b *BucketLimiter) bucket(id string) *rate.Limiter {
+ b.bucketMu.Lock()
+ bucket, ok := b.buckets[id]
+ if !ok {
+ bucket = rate.NewLimiter(b.freq, b.burst)
+ b.buckets[id] = bucket
+ }
+ b.bucketMu.Unlock()
+ return bucket
+}
+
+type handler struct {
+ next http.Handler
+ limited http.Handler
+ f func(*http.Request) string
+
+ limiter *BucketLimiter
+}
+
+func Handler(next, limited http.Handler, freq, burst int, f func(*http.Request) string) http.Handler {
+ return &handler{
+ next: next,
+ limited: limited,
+ f: f,
+ limiter: NewBucketLimiter(freq, burst),
+ }
+}
+
+func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ id := h.f(r)
+ if !h.limiter.Allow(id) {
+ h.limited.ServeHTTP(w, r)
+ return
+ }
+ h.next.ServeHTTP(w, r)
+}
+
+func RemoteAddrID(r *http.Request) string {
+ return r.RemoteAddr
+}
+
+func AuthUserID(r *http.Request) string {
+ user, _, _ := r.BasicAuth()
+ return user
+}
--- /dev/null
+// Package reqid creates request IDs and stores them in Contexts.
+package reqid
+
+import (
+ "context"
+ "crypto/rand"
+ "encoding/hex"
+ "net/http"
+
+ "github.com/bytom/log"
+)
+
+// key is an unexported type for keys defined in this package.
+// This prevents collisions with keys defined in other packages.
+type key int
+
+const (
+ // reqIDKey is the key for request IDs in Contexts. It is
+ // unexported; clients use NewContext and FromContext
+ // instead of using this key directly.
+ reqIDKey key = iota
+ // subReqIDKey is the key for sub-request IDs in Contexts. It is
+ // unexported; clients use NewSubContext and FromSubContext
+ // instead of using this key directly.
+ subReqIDKey
+ // coreIDKey is the key for Chain-Core-ID request header field values.
+ // It is only for statistics; don't use it for authorization.
+ coreIDKey
+ // pathKey is the key for the request path being handled.
+ pathKey
+)
+
+// New generates a random request ID.
+func New() string {
+ // Given n IDs of length b bits, the probability that there will be a collision is bounded by
+ // the number of pairs of IDs multiplied by the probability that any pair might collide:
+ // p ≤ n(n - 1)/2 * 1/(2^b)
+ //
+ // We assume an upper bound of 1000 req/sec, which means that in a week there will be
+ // n = 1000 * 604800 requests. If l = 10, b = 8*10, then p ≤ 1.512e-7, which is a suitably
+ // low probability.
+ l := 10
+ b := make([]byte, l)
+ _, err := rand.Read(b)
+ if err != nil {
+ log.Printf(context.Background(), "error making reqID")
+ }
+ return hex.EncodeToString(b)
+}
+
+// NewContext returns a new Context that carries reqid.
+// It also adds a log prefix to print the request ID using
+// package chain/log.
+func NewContext(ctx context.Context, reqid string) context.Context {
+ ctx = context.WithValue(ctx, reqIDKey, reqid)
+ ctx = log.AddPrefixkv(ctx, "reqid", reqid)
+ return ctx
+}
+
+// FromContext returns the request ID stored in ctx,
+// if any.
+func FromContext(ctx context.Context) string {
+ reqID, _ := ctx.Value(reqIDKey).(string)
+ return reqID
+}
+
+// CoreIDFromContext returns the Chain-Core-ID stored in ctx,
+// or the empty string.
+func CoreIDFromContext(ctx context.Context) string {
+ id, _ := ctx.Value(coreIDKey).(string)
+ return id
+}
+
+// PathFromContext returns the HTTP path stored in ctx,
+// or the empty string.
+func PathFromContext(ctx context.Context) string {
+ path, _ := ctx.Value(pathKey).(string)
+ return path
+}
+
+// NewSubContext returns a new Context that carries subreqid
+// It also adds a log prefix to print the sub-request ID using
+// package chain/log.
+func NewSubContext(ctx context.Context, reqid string) context.Context {
+ ctx = context.WithValue(ctx, subReqIDKey, reqid)
+ ctx = log.AddPrefixkv(ctx, "subreqid", reqid)
+ return ctx
+}
+
+// FromSubContext returns the sub-request ID stored in ctx,
+// if any.
+func FromSubContext(ctx context.Context) string {
+ subReqID, _ := ctx.Value(subReqIDKey).(string)
+ return subReqID
+}
+
+func Handler(handler http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ ctx := req.Context()
+ // TODO(kr): take half of request ID from the client
+ id := New()
+ ctx = NewContext(ctx, id)
+ ctx = context.WithValue(ctx, pathKey, req.URL.Path)
+ if coreID := req.Header.Get("Chain-Core-ID"); coreID != "" {
+ ctx = context.WithValue(ctx, coreIDKey, coreID)
+ ctx = log.AddPrefixkv(ctx, "coreid", coreID)
+ }
+
+ defer func() {
+ if err := recover(); err != nil {
+ log.Printkv(ctx,
+ "message", "panic",
+ "remote-addr", req.RemoteAddr,
+ "error", err,
+ )
+ }
+ }()
+ w.Header().Add("Chain-Request-Id", id)
+ handler.ServeHTTP(w, req.WithContext(ctx))
+ })
+}
--- /dev/null
+package reqid
+
+import (
+ "bytes"
+ "context"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/bytom/log"
+)
+
+func TestPrintkvRequestID(t *testing.T) {
+ buf := new(bytes.Buffer)
+ log.SetOutput(buf)
+ defer log.SetOutput(os.Stdout)
+
+ log.Printkv(NewContext(context.Background(), "example-request-id"))
+
+ got := buf.String()
+ want := "reqid=example-request-id"
+ if !strings.Contains(got, want) {
+ t.Errorf("Result did not contain string:\ngot: %s\nwant: %s", got, want)
+ }
+}
--- /dev/null
+// Package static provides a handler for serving static assets from an in-memory
+// map.
+package static
+
+import (
+ "net/http"
+ "strings"
+ "time"
+)
+
+// use start time as a conservative bound for last-modified
+var lastMod = time.Now()
+
+type Handler struct {
+ Assets map[string]string
+
+ // Index is the name of an entry in Assets that should be used if the request
+ // path is empty (equivalent to requesting "/"). This is analogous to index
+ // documents commonly used in webservers. If Index is empty, it will be
+ // ignored.
+ Index string
+
+ // Default is the name of an entry in Assets that should be used if the
+ // the requested path does not exist in Assets. This is useful for
+ // delivering a common document (usually a frontend application script) that
+ // handles URL-based state on the client side. If Default is empty, it will be
+ // ignored.
+ Default string
+}
+
+func (h Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ output, ok := h.Assets[r.URL.Path]
+ if !ok && r.URL.Path == "" && h.Index != "" {
+ output = h.Assets[h.Index]
+ } else if !ok && h.Default != "" {
+ output = h.Assets[h.Default]
+ } else if !ok {
+ http.NotFound(rw, r)
+ return
+ }
+
+ // Some autogenerated documentation uses frames, e.g. Javadoc
+ rw.Header().Set("X-Frame-Options", "SAMEORIGIN")
+
+ http.ServeContent(rw, r, r.URL.Path, lastMod, strings.NewReader(output))
+}