func Example_handler() { c := xhandler.Chain{} host, _ := os.Hostname() conf := xlog.Config{ // Set some global env fields Fields: xlog.F{ "role": "my-service", "host": host, }, } // Install the logger handler with default output on the console c.UseC(xlog.NewHandler(conf)) // Plug the xlog handler's input to Go's default logger log.SetFlags(0) log.SetOutput(xlog.New(conf)) // Install some provided extra handler to set some request's context fields. // Thanks to those handler, all our logs will come with some pre-populated fields. c.UseC(xlog.RemoteAddrHandler("ip")) c.UseC(xlog.UserAgentHandler("user_agent")) c.UseC(xlog.RefererHandler("referer")) c.UseC(xlog.RequestIDHandler("req_id", "Request-Id")) // Here is your final handler h := c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { // Get the logger from the context. You can safely assume it will be always there, // if the handler is removed, xlog.FromContext will return a NopLogger l := xlog.FromContext(ctx) // Then log some errors if err := errors.New("some error from elsewhere"); err != nil { l.Errorf("Here is an error: %v", err) } // Or some info with fields l.Info("Something happend", xlog.F{ "user": "******", "status": "ok", }) })) http.Handle("/", h) if err := http.ListenAndServe(":8080", nil); err != nil { log.SetOutput(os.Stderr) // make sure we print to console log.Fatal(err) } }
func main() { // Create a REST API resource index index := resource.NewIndex() // Add a resource on /users[/:user_id] users := index.Bind("users", user, mem.NewHandler(), resource.Conf{ // We allow all REST methods // (rest.ReadWrite is a shortcut for []resource.Mode{resource.Create, resource.Read, resource.Update, resource.Delete, resource,List}) AllowedModes: resource.ReadWrite, }) // Bind a sub resource on /users/:user_id/posts[/:post_id] // and reference the user on each post using the "user" field of the posts resource. posts := users.Bind("posts", "user", post, mem.NewHandler(), resource.Conf{ // Posts can only be read, created and deleted, not updated AllowedModes: []resource.Mode{resource.Read, resource.List, resource.Create, resource.Delete}, }) // Add a friendly alias to public posts // (equivalent to /users/:user_id/posts?filter={"published":true}) posts.Alias("public", url.Values{"filter": []string{"{\"published\":true}"}}) // Create API HTTP handler for the resource graph api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } c := alice.New() // Add close notifier handler so context is cancelled when the client closes // the connection // c.Append(xhandler.CloseHandler) // Add timeout handler // c.Append(xhandler.TimeoutHandler(2 * time.Second)) // Install a logger (see https://github.com/rs/xlog) c = c.Append(xlog.NewHandler(xlog.Config{})) resource.LoggerLevel = resource.LogLevelDebug resource.Logger = func(ctx context.Context, level resource.LogLevel, msg string, fields map[string]interface{}) { xlog.FromContext(ctx).OutputF(xlog.Level(level), 2, msg, fields) } // Log API access c = c.Append(xaccess.NewHandler()) // Add CORS support with passthrough option on so rest-layer can still // handle OPTIONS method c = c.Append(cors.New(cors.Options{OptionsPassthrough: true}).Handler) // Bind the API under /api/ path http.Handle("/api/", http.StripPrefix("/api/", c.Then(api))) // Serve it log.Print("Serving API on http://localhost:8080") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
// SendError writes a REST formated error on the http.ResponseWriter func (s DefaultResponseSender) SendError(ctx context.Context, headers http.Header, err error, skipBody bool) (context.Context, interface{}) { code := 500 message := "Server Error" if err != nil { message = err.Error() if e, ok := err.(*Error); ok { code = e.Code } } if code >= 500 { xlog.FromContext(ctx).Errorf("Server error: %v", err) } if !skipBody { payload := map[string]interface{}{ "code": code, "message": message, } if e, ok := err.(*Error); ok { if e.Issues != nil { payload["issues"] = e.Issues } } return ctx, payload } return ctx, nil }
// EmptyBody writes a http.StatusServiceUnavailable to indicate a temporary service failure // This response should make the client retry. func EmptyBody(ctx context.Context, rw http.ResponseWriter, r *http.Request) { l := xlog.FromContext(ctx) l.Info("emptyBody") rw.WriteHeader(http.StatusServiceUnavailable) rw.Write(nil) }
// LogHandler instantiates a new xlog HTTP handler using the given log. func LogHandler() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ptw := passThroughResponseWriter{200, w} start := time.Now() next.ServeHTTP(&ptw, r) xlog.FromContext(r.Context()).Info(http.StatusText(ptw.StatusCode), xlog.F{ "duration": time.Now().Sub(start).String(), "status": ptw.StatusCode, }) }) } }
// Check get the riskScore and aggregate characteristics. func (f *Forensiq) Check(ctx context.Context, creq CheckRequest) (CheckResponse, error) { var ( uri *url.URL cresp CheckResponse err error log = xlog.FromContext(ctx) sts = xstats.FromContext(ctx) ) var req *http.Request { uri, err = url.Parse(f.Host) if err != nil { log.Errorf("error parsing the URL: %s%v", err, xlog.F{"host": f.Host}) return CheckResponse{}, err } uri.Path = "/check" v := creq.toValues() v.Set("ck", f.ClientKey) v.Set("output", "JSON") uri.RawQuery = v.Encode() req, err = http.NewRequest("GET", uri.String(), nil) if err != nil { return CheckResponse{}, err } req.Header.Set("Content-Type", "application/json") } { begin := time.Now() resp, err := ctxhttp.Do(ctx, f.httpClient, req) if err != nil { return CheckResponse{}, err } defer resp.Body.Close() sts.Timing("forensiq.request_time", time.Since(begin), "request:check", "status:"+responseStatus(ctx, resp.StatusCode), "status_code:"+strconv.Itoa(resp.StatusCode), ) if resp.StatusCode == http.StatusForbidden { log.Errorf("client key is invalid%v", xlog.F{"client_key": f.ClientKey}) return CheckResponse{}, ErrInvalidClientKey } if err := json.NewDecoder(resp.Body).Decode(&cresp); err != nil { return CheckResponse{}, err } } return cresp, nil }
func eternalRequest(ctx context.Context, rw http.ResponseWriter, r *http.Request) { l := xlog.FromContext(ctx) hours := time.Duration(rand.Intn(24)) * time.Hour l.Infof("eternalRequest %v", hours) select { case <-time.After(hours): rw.Write(response) return case <-ctx.Done(): l.Infof("Connection dead") return } }
// Send sends headers with the given status and marshal the data in JSON func (s DefaultResponseSender) Send(ctx context.Context, w http.ResponseWriter, status int, headers http.Header, body interface{}) { headers.Set("Content-Type", "application/json") // Apply headers to the response for key, values := range headers { for _, value := range values { w.Header().Add(key, value) } } w.WriteHeader(status) if body != nil { j, err := json.Marshal(body) if err != nil { w.WriteHeader(500) xlog.FromContext(ctx).Errorf("Can't build response: %v", err) msg := fmt.Sprintf("Can't build response: %s", strconv.Quote(err.Error())) w.Write([]byte(fmt.Sprintf("{\"code\": 500, \"msg\": \"%s\"}", msg))) return } if _, err = w.Write(j); err != nil { xlog.FromContext(ctx).Errorf("Can't send response: %v", err) } } }
func longRequest(ctx context.Context, rw http.ResponseWriter, r *http.Request) { l := xlog.FromContext(ctx) minutes := time.Duration(rand.Intn(60)+30) * time.Minute l.Infof("longRequest %v", minutes) select { case <-time.After(minutes): rw.Write(response) return case <-ctx.Done(): // make sure we cleanup if the client hangs up l.Infof("Connection dead") return } }
func normalResponse(ctx context.Context, rw http.ResponseWriter, r *http.Request) { l := xlog.FromContext(ctx) millis := time.Duration(rand.Intn(150)+50) * time.Millisecond l.Infof("normal response %vms", millis) select { case <-time.After(millis): rw.Write(response) return case <-ctx.Done(): l.Infof("Connection dead") return } }
func Example_log() { ctx := context.TODO() // got from xhandler l := xlog.FromContext(ctx) // Log a simple message l.Debug("message") if err := errors.New("some error"); err != nil { l.Errorf("Some error happened: %v", err) } // With optional fields l.Debugf("foo %s", "bar", xlog.F{ "field": "value", }) }
// NewJWTHandler parse and validates JWT token if present and store it in the net/context func NewJWTHandler(users *resource.Resource, jwtKeyFunc jwt.Keyfunc) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token, err := request.ParseFromRequest(r, request.OAuth2Extractor, jwtKeyFunc) if err == request.ErrNoTokenInRequest { // If no token is found, let REST Layer hooks decide if the resource is public or not next.ServeHTTP(w, r) return } if err != nil || !token.Valid { // Here you may want to return JSON error http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } claims := token.Claims.(jwt.MapClaims) userID, ok := claims["user_id"].(string) if !ok || userID == "" { // The provided token is malformed, user_id claim is missing http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } // Lookup the user by its id ctx := r.Context() user, err := users.Get(ctx, userID) if user != nil && err == resource.ErrUnauthorized { // Ignore unauthorized errors set by ourselves (see AuthResourceHook) err = nil } if err != nil { // If user resource storage handler returned an error, respond with an error if err == resource.ErrNotFound { http.Error(w, "Invalid credential", http.StatusForbidden) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } return } // Store it into the request's context ctx = NewContextWithUser(ctx, user) r = r.WithContext(ctx) // If xlog is setup, store the user as logger field xlog.FromContext(ctx).SetField("user_id", user.ID) next.ServeHTTP(w, r) }) } }
// NewHandler returns a handler that log access information about each request performed // on the provided sub-handlers. Uses context's github.com/rs/xlog and // github.com/rs/xstats if present for logging. func NewHandler() func(next xhandler.HandlerC) xhandler.HandlerC { return func(next xhandler.HandlerC) xhandler.HandlerC { return xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { // Time request reqStart := time.Now() // Sniff the status and content size for logging lw := mutil.WrapWriter(w) // Call the next handler next.ServeHTTPC(ctx, lw, r) // Conpute request duration reqDur := time.Since(reqStart) // Get request status status := responseStatus(ctx, lw.Status()) // Log request stats sts := xstats.FromContext(ctx) tags := []string{ "status:" + status, "status_code:" + strconv.Itoa(lw.Status()), } sts.Timing("request_time", reqDur, tags...) sts.Histogram("request_size", float64(lw.BytesWritten()), tags...) // Log access info log := xlog.FromContext(ctx) log.Infof("%s %s %03d", r.Method, ellipsize(r.URL.String(), 100), lw.Status(), xlog.F{ "method": r.Method, "uri": r.URL.String(), "type": "access", "status": status, "status_code": lw.Status(), "duration": reqDur.Seconds(), "size": lw.BytesWritten(), }) }) } }
func main() { // Create a REST API resource index index := resource.NewIndex() // Bind user on /users users := index.Bind("users", user, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) // Init the db with some users (user registration is not handled by this example) secret, _ := schema.Password{}.Validate("secret") users.Insert(context.Background(), []*resource.Item{ {ID: "admin", Updated: time.Now(), ETag: "abcd", Payload: map[string]interface{}{ "id": "admin", "name": "Dilbert", "password": secret, }}, {ID: "john", Updated: time.Now(), ETag: "efgh", Payload: map[string]interface{}{ "id": "john", "name": "John Doe", "password": secret, }}, }) // Bind post on /posts posts := index.Bind("posts", post, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) // Protect resources users.Use(AuthResourceHook{UserField: "id"}) posts.Use(AuthResourceHook{UserField: "user"}) // Create API HTTP handler for the resource graph api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } // Setup logger c := alice.New() c = c.Append(xlog.NewHandler(xlog.Config{})) c = c.Append(xaccess.NewHandler()) c = c.Append(xlog.RequestHandler("req")) c = c.Append(xlog.RemoteAddrHandler("ip")) c = c.Append(xlog.UserAgentHandler("ua")) c = c.Append(xlog.RefererHandler("ref")) c = c.Append(xlog.RequestIDHandler("req_id", "Request-Id")) resource.LoggerLevel = resource.LogLevelDebug resource.Logger = func(ctx context.Context, level resource.LogLevel, msg string, fields map[string]interface{}) { xlog.FromContext(ctx).OutputF(xlog.Level(level), 2, msg, fields) } // Setup auth middleware c = c.Append(NewBasicAuthHandler(users)) // Bind the API under / http.Handle("/", c.Then(api)) // Serve it log.Print("Serving API on http://localhost:8080") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
func Example() { var ( // Define a user resource schema user = schema.Schema{ Fields: schema.Fields{ "id": { Required: true, // When a field is read-only, on default values or hooks can // set their value. The client can't change it. ReadOnly: true, // This is a field hook called when a new user is created. // The schema.NewID hook is a provided hook to generate a // unique id when no value is provided. OnInit: schema.NewID, // The Filterable and Sortable allows usage of filter and sort // on this field in requests. Filterable: true, Sortable: true, Validator: &schema.String{ Regexp: "^[0-9a-f]{32}$", }, }, "created": { Required: true, ReadOnly: true, Filterable: true, Sortable: true, OnInit: schema.Now, Validator: &schema.Time{}, }, "updated": { Required: true, ReadOnly: true, Filterable: true, Sortable: true, OnInit: schema.Now, // The OnUpdate hook is called when the item is edited. Here we use // provided Now hook which just return the current time. OnUpdate: schema.Now, Validator: &schema.Time{}, }, // Define a name field as required with a string validator "name": { Required: true, Filterable: true, Validator: &schema.String{ MaxLen: 150, }, }, }, } // Define a post resource schema post = schema.Schema{ Fields: schema.Fields{ // schema.*Field are shortcuts for common fields (identical to users' same fields) "id": schema.IDField, "created": schema.CreatedField, "updated": schema.UpdatedField, // Define a user field which references the user owning the post. // See bellow, the content of this field is enforced by the fact // that posts is a sub-resource of users. "user": { Required: true, Filterable: true, Validator: &schema.Reference{ Path: "users", }, }, "public": { Filterable: true, Validator: &schema.Bool{}, }, // Sub-documents are handled via a sub-schema "meta": { Schema: &schema.Schema{ Fields: schema.Fields{ "title": { Required: true, Validator: &schema.String{ MaxLen: 150, }, }, "body": { Validator: &schema.String{ MaxLen: 100000, }, }, }, }, }, }, } ) // Create a REST API root resource index := resource.NewIndex() // Add a resource on /users[/:user_id] users := index.Bind("users", user, mem.NewHandler(), resource.Conf{ // We allow all REST methods // (rest.ReadWrite is a shortcut for []rest.Mode{Create, Read, Update, Delete, List}) AllowedModes: resource.ReadWrite, }) // Bind a sub resource on /users/:user_id/posts[/:post_id] // and reference the user on each post using the "user" field of the posts resource. posts := users.Bind("posts", "user", post, mem.NewHandler(), resource.Conf{ // Posts can only be read, created and deleted, not updated AllowedModes: []resource.Mode{resource.Read, resource.List, resource.Create, resource.Delete}, }) // Add a friendly alias to public posts // (equivalent to /users/:user_id/posts?filter={"public":true}) posts.Alias("public", url.Values{"filter": []string{"{\"public\"=true}"}}) // Create API HTTP handler for the resource graph api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } // Init an alice handler chain (use your preferred one) c := alice.New() // Add close notifier handler so context is cancelled when the client closes // the connection //c.Append(xhandler.CloseHandler) // Add timeout handler //c.Append(xhandler.TimeoutHandler(2 * time.Second)) // Install a logger (see https://github.com/rs/xlog) c = c.Append(xlog.NewHandler(xlog.Config{})) resource.LoggerLevel = resource.LogLevelDebug resource.Logger = func(ctx context.Context, level resource.LogLevel, msg string, fields map[string]interface{}) { xlog.FromContext(ctx).OutputF(xlog.Level(level), 2, msg, fields) } // Log API access c = c.Append(xaccess.NewHandler()) // Add CORS support with passthrough option on so rest-layer can still // handle OPTIONS method c = c.Append(cors.New(cors.Options{OptionsPassthrough: true}).Handler) // Bind the API under /api/ path http.Handle("/api/", http.StripPrefix("/api/", c.Then(api))) // Serve it log.Print("Serving API on http://localhost:8080") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
func main() { index := resource.NewIndex() users := index.Bind("users", user, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) users.Alias("admin", url.Values{"filter": []string{`{"admin": true}`}}) posts := index.Bind("posts", post, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) posts.Bind("followers", "post", postFollower, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) // Create API HTTP handler for the resource graph api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } // Setup logger c := alice.New() c = c.Append(xlog.NewHandler(xlog.Config{})) c = c.Append(xaccess.NewHandler()) c = c.Append(xlog.RequestHandler("req")) c = c.Append(xlog.RemoteAddrHandler("ip")) c = c.Append(xlog.UserAgentHandler("ua")) c = c.Append(xlog.RefererHandler("ref")) c = c.Append(xlog.RequestIDHandler("req_id", "Request-Id")) resource.LoggerLevel = resource.LogLevelDebug resource.Logger = func(ctx context.Context, level resource.LogLevel, msg string, fields map[string]interface{}) { xlog.FromContext(ctx).OutputF(xlog.Level(level), 2, msg, fields) } // Bind the API under /api/ path http.Handle("/api/", http.StripPrefix("/api/", c.Then(api))) // Create and bind the graphql endpoint graphql, err := graphql.NewHandler(index) if err != nil { log.Fatal(err) } http.Handle("/graphql", c.Then(graphql)) http.HandleFunc("/graphiql", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` <!DOCTYPE html> <html> <head> <style> html, body {height: 100%; margin: 0; overflow: hidden; width: 100%;} </style> <link href="//cdn.jsdelivr.net/graphiql/0.4.9/graphiql.css" rel="stylesheet" /> <script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script> <script src="//cdn.jsdelivr.net/react/0.14.7/react.min.js"></script> <script src="//cdn.jsdelivr.net/react/0.14.7/react-dom.min.js"></script> <script src="//cdn.jsdelivr.net/graphiql/0.4.9/graphiql.min.js"></script> </head> <body> <script> // Collect the URL parameters var parameters = {}; window.location.search.substr(1).split('&').forEach(function (entry) { var eq = entry.indexOf('='); if (eq >= 0) { parameters[decodeURIComponent(entry.slice(0, eq))] = decodeURIComponent(entry.slice(eq + 1)); } }); // Produce a Location query string from a parameter object. function locationQuery(params) { return '/graphql?' + Object.keys(params).map(function (key) { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); }).join('&'); } // Derive a fetch URL from the current URL, sans the GraphQL parameters. var graphqlParamNames = { query: true, variables: true, operationName: true }; var otherParams = {}; for (var k in parameters) { if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) { otherParams[k] = parameters[k]; } } var fetchURL = locationQuery(otherParams); // Defines a GraphQL fetcher using the fetch API. function graphQLFetcher(graphQLParams) { return fetch(fetchURL, { method: 'post', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(graphQLParams), credentials: 'include', }).then(function (response) { return response.text(); }).then(function (responseBody) { try { return JSON.parse(responseBody); } catch (error) { return responseBody; } }); } // When the query and variables string is edited, update the URL bar so // that it can be easily shared. function onEditQuery(newQuery) { parameters.query = newQuery; updateURL(); } function onEditVariables(newVariables) { parameters.variables = newVariables; updateURL(); } function updateURL() { history.replaceState(null, null, locationQuery(parameters)); } // Render <GraphiQL /> into the body. React.render( React.createElement(GraphiQL, { fetcher: graphQLFetcher, onEditQuery: onEditQuery, onEditVariables: onEditVariables, defaultQuery: "{\ postsList{\ i: id,\ m: meta{\ t: title,\ b: body},\ thumb_small_url: thumbnail_url(height:80)\ }\ }", }), document.body ); </script> </body> </html>`)) }) // Inject some fixtures fixtures := [][]string{ {"PUT", "/users/johndoe", `{"name": "John Doe", "ip": "1.2.3.4", "password": "******", "admin": true}`}, {"PUT", "/users/fan1", `{"name": "Fan 1", "ip": "1.2.3.4", "password": "******"}}`}, {"PUT", "/users/fan2", `{"name": "Fan 2", "ip": "1.2.3.4", "password": "******"}}`}, {"PUT", "/users/fan3", `{"name": "Fan 3", "ip": "1.2.3.4", "password": "******"}}`}, {"PUT", "/users/fan4", `{"name": "Fan 4", "ip": "1.2.3.4", "password": "******"}}`}, {"PUT", "/posts/ar5qrgukj5l7a6eq2ps0", `{ "user": "******", "thumbnail_url": "http://dom.com/image.png", "meta": { "title": "First Post", "body": "This is my first post" } }`}, {"POST", "/posts/ar5qrgukj5l7a6eq2ps0/followers", `{"user": "******"}`}, {"POST", "/posts/ar5qrgukj5l7a6eq2ps0/followers", `{"user": "******"}`}, {"POST", "/posts/ar5qrgukj5l7a6eq2ps0/followers", `{"user": "******"}`}, } for _, fixture := range fixtures { req, err := http.NewRequest(fixture[0], fixture[1], strings.NewReader(fixture[2])) if err != nil { log.Fatal(err) } w := httptest.NewRecorder() api.ServeHTTP(w, req) if w.Code >= 400 { log.Fatalf("Error returned for `%s %s`: %v", fixture[0], fixture[1], w) } } // Serve it log.Print("Serving API on http://localhost:8080") log.Print("Visit http://localhost:8080/graphiql for a GraphiQL UI") log.Println("Play with (httpie):\n", "- http :8080/graphql query=='{postsList{id,thumb_s_url:thumbnail_url(height:80)}}'\n", "- http :8080/graphql query=='{postsList{i:id,m:meta{t:title, b:body},thumb_small_url:thumbnail_url(height:80)}}'\n", "- http :8080/graphql query=='{postsList{id,meta{title},user{id,name}}}'\n", "- http :8080/graphql query=='{posts(id:\"ar5qrgukj5l7a6eq2ps0\"){followers{post{id,meta{title}},user{id,name}}}}'\n", "- http :8080/graphql query=='{posts(id:\"ar5qrgukj5l7a6eq2ps0\"){id,meta{title},followers(limit:2){user{id,name}}}}'") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
func main() { flag.Parse() // Create a REST API resource index index := resource.NewIndex() // Bind user on /users users := index.Bind("users", user, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) // Init the db with some users (user registration is not handled by this example) secret, _ := schema.Password{}.Validate("secret") users.Insert(context.Background(), []*resource.Item{ {ID: "admin", Updated: time.Now(), ETag: "abcd", Payload: map[string]interface{}{ "id": "jack", "name": "Jack Sparrow", "password": secret, }}, {ID: "john", Updated: time.Now(), ETag: "efgh", Payload: map[string]interface{}{ "id": "john", "name": "John Doe", "password": secret, }}, }) // Bind post on /posts posts := index.Bind("posts", post, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) // Protect resources users.Use(AuthResourceHook{UserField: "id"}) posts.Use(AuthResourceHook{UserField: "user"}) // Create API HTTP handler for the resource graph api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } // Setup logger c := alice.New() c = c.Append(xlog.NewHandler(xlog.Config{})) c = c.Append(xaccess.NewHandler()) c = c.Append(xlog.RequestHandler("req")) c = c.Append(xlog.RemoteAddrHandler("ip")) c = c.Append(xlog.UserAgentHandler("ua")) c = c.Append(xlog.RefererHandler("ref")) c = c.Append(xlog.RequestIDHandler("req_id", "Request-Id")) resource.LoggerLevel = resource.LogLevelDebug resource.Logger = func(ctx context.Context, level resource.LogLevel, msg string, fields map[string]interface{}) { xlog.FromContext(ctx).OutputF(xlog.Level(level), 2, msg, fields) } // Setup auth middleware jwtSecretBytes := []byte(*jwtSecret) c = c.Append(NewJWTHandler(users, func(t *jwt.Token) (interface{}, error) { println("paf") if t.Method != jwt.SigningMethodHS256 { return nil, jwt.ErrInvalidKey } return jwtSecretBytes, nil })) // Bind the API under / http.Handle("/", c.Then(api)) // Demo tokens jackToken := jwt.New(jwt.SigningMethodHS256) jackClaims := jackToken.Claims.(jwt.MapClaims) jackClaims["user_id"] = "jack" jackTokenString, err := jackToken.SignedString(jwtSecretBytes) if err != nil { log.Fatal(err) } johnToken := jwt.New(jwt.SigningMethodHS256) johnClaims := johnToken.Claims.(jwt.MapClaims) johnClaims["user_id"] = "john" johnTokenString, err := johnToken.SignedString(jwtSecretBytes) if err != nil { log.Fatal(err) } // Serve it log.Print("Serving API on http://localhost:8080") log.Printf("Your token secret is %q, change it with the `-jwt-secret' flag", *jwtSecret) log.Print("Play with tokens:\n", "\n", "- http :8080/posts access_token==", johnTokenString, " title=\"John's post\"\n", "- http :8080/posts access_token==", johnTokenString, "\n", "- http :8080/posts\n", "\n", "- http :8080/posts access_token==", jackTokenString, " title=\"Jack's post\"\n", "- http :8080/posts access_token==", jackTokenString, "\n", ) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
func unoconvHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { l := xlog.FromContext(ctx) //The whole request body is parsed and up to a total of 34MB bytes of its file parts are stored in memory, //with the remainder stored on disk in temporary files. r.ParseMultipartForm(32 << 20) file, handler, err := r.FormFile("file") if err != nil { l.Error(err) return } defer file.Close() //add the filename to access log l.SetField("filename", handler.Filename) //create a temporary file and copy the file from the form to it tempfile, err := ioutil.TempFile(os.TempDir(), "unoconv-api") if err != nil { l.Error(err) return } switch filepath.Ext(handler.Filename) { case ".txt": //read the files content data, err := ioutil.ReadAll(file) if err != nil { l.Error(err) return } //try to convert the textfile (data) to utf-8 and write it to tempfile charset, err := toUTF8(data, tempfile) l.SetField("charset", charset) l.SetField("convertedToUTF8", true) if err != nil { //Could not convert to utf-8, write the original data to tempfile l.Error(err) l.SetField("convertedToUTF8", false) io.Copy(tempfile, bytes.NewBuffer(data)) } default: io.Copy(tempfile, file) } tempfile.Close() //append the file extension to the temporary file's name filename := tempfile.Name() + filepath.Ext(handler.Filename) os.Rename(tempfile.Name(), filename) defer os.Remove(filename) //Run unoconv to convert the file //unoconv's stdout is plugged directly to the httpResponseWriter err = uno.convert(filename, xmux.Param(ctx, "filetype"), w) if err != nil { l.Error(err) return } }
func main() { index := resource.NewIndex() index.Bind("posts", post, restrix.Wrap("posts", mem.NewHandler()), resource.Conf{ AllowedModes: resource.ReadWrite, }) // Create API HTTP handler for the resource graph api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } // Setup logger c := alice.New() c = c.Append(xlog.NewHandler(xlog.Config{})) c = c.Append(xaccess.NewHandler()) resource.LoggerLevel = resource.LogLevelDebug resource.Logger = func(ctx context.Context, level resource.LogLevel, msg string, fields map[string]interface{}) { xlog.FromContext(ctx).OutputF(xlog.Level(level), 2, msg, fields) } // Bind the API under the root path http.Handle("/", c.Then(api)) // Configure hystrix commands hystrix.Configure(map[string]hystrix.CommandConfig{ "posts.MultiGet": { Timeout: 500, MaxConcurrentRequests: 200, ErrorPercentThreshold: 25, }, "posts.Find": { Timeout: 1000, MaxConcurrentRequests: 100, ErrorPercentThreshold: 25, }, "posts.Insert": { Timeout: 1000, MaxConcurrentRequests: 50, ErrorPercentThreshold: 25, }, "posts.Update": { Timeout: 1000, MaxConcurrentRequests: 50, ErrorPercentThreshold: 25, }, "posts.Delete": { Timeout: 1000, MaxConcurrentRequests: 10, ErrorPercentThreshold: 10, }, "posts.Clear": { Timeout: 10000, MaxConcurrentRequests: 5, ErrorPercentThreshold: 10, }, }) // Start the metrics stream handler hystrixStreamHandler := hystrix.NewStreamHandler() hystrixStreamHandler.Start() log.Print("Serving Hystrix metrics on http://localhost:8081") go http.ListenAndServe(net.JoinHostPort("", "8081"), hystrixStreamHandler) // Inject some fixtures fixtures := [][]string{ {"POST", "/posts", `{"title": "First Post", "body": "This is my first post"}`}, {"POST", "/posts", `{"title": "Second Post", "body": "This is my second post"}`}, {"POST", "/posts", `{"title": "Third Post", "body": "This is my third post"}`}, } for _, fixture := range fixtures { req, err := http.NewRequest(fixture[0], fixture[1], strings.NewReader(fixture[2])) if err != nil { log.Fatal(err) } w := httptest.NewRecorder() api.ServeHTTP(w, req) if w.Code >= 400 { log.Fatalf("Error returned for `%s %s`: %v", fixture[0], fixture[1], w) } } // Serve it log.Print("Serving API on http://localhost:8080") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
func main() { index := resource.NewIndex() index.Bind("users", user, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) posts := index.Bind("posts", post, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) posts.Bind("followers", "post", postFollower, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, }) // Create API HTTP handler for the resource graph api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } // Setup logger c := alice.New() c = c.Append(xlog.NewHandler(xlog.Config{})) c = c.Append(xaccess.NewHandler()) resource.LoggerLevel = resource.LogLevelDebug resource.Logger = func(ctx context.Context, level resource.LogLevel, msg string, fields map[string]interface{}) { xlog.FromContext(ctx).OutputF(xlog.Level(level), 2, msg, fields) } // Bind the API under the root path http.Handle("/", c.Then(api)) // Inject some fixtures fixtures := [][]string{ {"PUT", "/users/johndoe", `{"name": "John Doe", "ip": "1.2.3.4", "password": "******"}`}, {"PUT", "/users/fan1", `{"name": "Fan 1", "ip": "1.2.3.4", "password": "******"}}`}, {"PUT", "/users/fan2", `{"name": "Fan 2", "ip": "1.2.3.4", "password": "******"}}`}, {"PUT", "/users/fan3", `{"name": "Fan 3", "ip": "1.2.3.4", "password": "******"}}`}, {"PUT", "/users/fan4", `{"name": "Fan 4", "ip": "1.2.3.4", "password": "******"}}`}, {"PUT", "/posts/ar5qrgukj5l7a6eq2ps0", `{ "user": "******", "thumbnail_url": "http://dom.com/image.png", "meta": { "title": "First Post", "body": "This is my first post" } }`}, {"POST", "/posts/ar5qrgukj5l7a6eq2ps0/followers", `{"user": "******"}`}, {"POST", "/posts/ar5qrgukj5l7a6eq2ps0/followers", `{"user": "******"}`}, {"POST", "/posts/ar5qrgukj5l7a6eq2ps0/followers", `{"user": "******"}`}, } for _, fixture := range fixtures { req, err := http.NewRequest(fixture[0], fixture[1], strings.NewReader(fixture[2])) if err != nil { log.Fatal(err) } w := httptest.NewRecorder() api.ServeHTTP(w, req) if w.Code >= 400 { log.Fatalf("Error returned for `%s %s`: %v", fixture[0], fixture[1], w) } } // Serve it log.Print("Serving API on http://localhost:8080") log.Println("Play with (httpie):\n", "- http :8080/posts fields=='id,thumb_s_url:thumbnail_url(height:80)'\n", "- http :8080/posts fields=='i:id,m:meta{t:title,b:body},thumb_small_url:thumbnail_url(height:80)'\n", "- http :8080/posts fields=='id,meta,user{id,name}'\n", "- http :8080/posts/ar5qrgukj5l7a6eq2ps0/followers fields=='post{id,meta{title}},user{id,name}'\n", "- http :8080/posts/ar5qrgukj5l7a6eq2ps0 fields=='id,meta{title},followers(limit:2){user{id,name}}'") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }