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 ExampleNewHandler() { c := xhandler.Chain{} // Install the metric handler with dogstatsd backend client and some env tags flushInterval := 5 * time.Second tags := []string{"role:my-service"} statsdWriter, err := net.Dial("udp", "127.0.0.1:8126") if err != nil { log.Fatal(err) } c.UseC(xstats.NewHandler(dogstatsd.New(statsdWriter, flushInterval), tags)) // Here is your handler h := c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { // Get the xstats request's instance from the context. You can safely assume it will // be always there, if the handler is removed, xstats.FromContext will return a nop // instance. m := xstats.FromContext(ctx) // Count something m.Count("requests", 1, "route:index") })) http.Handle("/", h) if err := http.ListenAndServe(":8080", nil); err != nil { 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", resource.New(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", resource.New(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) } // Init a xhandler chain (see https://github.com/rs/xhandler) c := xhandler.Chain{} // Add close notifier handler so context is cancelled when the client closes // the connection c.UseC(xhandler.CloseHandler) // Add timeout handler c.UseC(xhandler.TimeoutHandler(2 * time.Second)) // Install a logger (see https://github.com/rs/xlog) c.UseC(xlog.NewHandler(xlog.Config{})) // Log API access c.UseC(xaccess.NewHandler()) // Add CORS support with passthrough option on so rest-layer can still // handle OPTIONS method c.UseC(cors.New(cors.Options{OptionsPassthrough: true}).HandlerC) // Bind the API under /api/ path http.Handle("/api/", http.StripPrefix("/api/", c.Handler(api))) // Serve it log.Print("Serving API on http://localhost:8080") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
func main() { c := xhandler.Chain{} // Use default options c.UseC(cors.Default().HandlerC) mux := http.NewServeMux() mux.Handle("/", c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte("{\"hello\": \"world\"}")) }))) http.ListenAndServe(":8080", mux) }
func ExampleIf() { c := xhandler.Chain{} // Add a timeout handler only if the URL path matches a prefix c.UseC(xhandler.If( func(ctx context.Context, w http.ResponseWriter, r *http.Request) bool { return strings.HasPrefix(r.URL.Path, "/with-timeout/") }, xhandler.TimeoutHandler(2*time.Second), )) http.Handle("/", c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }))) }
func main() { runtime.GOMAXPROCS(runtime.NumCPU()) env, err := getEnviron() if err != nil { log.Error(err) os.Exit(1) } l, err := log.ParseLevel(env.logLevel) if err != nil { l = log.ErrorLevel } log.SetLevel(l) log.Infof("Service %s started", serviceID) log.Infof("%s=%s", tmpDirEnvar, env.tmpDir) log.Infof("%s=%s", authServerEnvar, env.authServer) log.Infof("%s=%s", dataServerEnvar, env.dataServer) log.Infof("%s=%s", metaServerEnvar, env.metaServer) log.Infof("%s=%d\n", portEnvar, env.port) log.Infof("%s=%s\n", sharedSecretEnvar, "******") p := &newServerParams{} p.authServer = env.authServer p.dataServer = env.dataServer p.metaServer = env.metaServer p.sharedSecret = env.sharedSecret p.tmpDir = env.tmpDir // Create chunk tmp dir if err := os.MkdirAll(p.tmpDir, 0644); err != nil { log.Error(err) os.Exit(1) } srv, err := newServer(p) if err != nil { log.Error(err) os.Exit(1) } c := xhandler.Chain{} c.UseC(xhandler.CloseHandler) http.Handle(endPoint, c.Handler(srv)) log.Error(http.ListenAndServe(fmt.Sprintf(":%d", env.port), nil)) }
func main() { runtime.GOMAXPROCS(runtime.NumCPU()) c := xhandler.Chain{} c.UseC(xhandler.CloseHandler) env, err := getEnviron() if err != nil { log.Error(err) os.Exit(1) } l, err := log.ParseLevel(env.logLevel) if err != nil { l = log.ErrorLevel } log.SetLevel(l) log.Infof("Service %s started", serviceID) printEnviron(env) p := &newServerParams{} p.dataDir = env.dataDir p.tmpDir = env.tmpDir p.checksum = env.checksum p.prop = env.prop p.sharedSecret = env.sharedSecret // Create data and tmp dirs if err := os.MkdirAll(p.dataDir, 0644); err != nil { log.Error(err) os.Exit(1) } if err := os.MkdirAll(p.tmpDir, 0644); err != nil { log.Error(err) os.Exit(1) } srv, err := newServer(p) if err != nil { log.Error(err) os.Exit(1) } http.Handle(endPoint, c.Handler(srv)) log.Error(http.ListenAndServe(fmt.Sprintf(":%d", env.port), nil)) }
func ExampleMux() { c := xhandler.Chain{} // Append a context-aware middleware handler c.UseC(xhandler.CloseHandler) // Another context-aware middleware handler c.UseC(xhandler.TimeoutHandler(2 * time.Second)) mux := xmux.New() // Use c.Handler to terminate the chain with your final handler mux.GET("/welcome/:name", xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome %s!", xmux.Param(ctx, "name")) })) if err := http.ListenAndServe(":8080", c.Handler(mux)); err != nil { log.Fatal(err) } }
func ExampleChain() { c := xhandler.Chain{} // Append a context-aware middleware handler c.UseC(xhandler.CloseHandler) // Mix it with a non-context-aware middleware handler c.Use(cors.Default().Handler) // Another context-aware middleware handler c.UseC(xhandler.TimeoutHandler(2 * time.Second)) mux := http.NewServeMux() // Use c.Handler to terminate the chain with your final handler mux.Handle("/", c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the home page!") }))) // You can reuse the same chain for other handlers mux.Handle("/api", c.Handler(xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to the API!") }))) }
// setupHandlerChain does plumbing for logging and such. func setupHandlerChain(c *xhandler.Chain) { host, _ := os.Hostname() conf := xlog.Config{ // Log info level and higher Level: xlog.LevelInfo, // Set some global env fields Fields: xlog.F{ "role": "my-shitty-service", "host": host, }, // Output everything on console Output: xlog.NewOutputChannel(xlog.NewConsoleOutput()), } // Add close notifier handler so context is cancelled when the client closes // the connection c.UseC(xhandler.CloseHandler) // Install the logger handler c.UseC(xlog.NewHandler(conf)) // Add timeout handler (HAHA) //c.UseC(xhandler.TimeoutHandler(2 * time.Second)) // 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.MethodHandler("method")) c.UseC(xlog.URLHandler("url")) c.UseC(xlog.RemoteAddrHandler("ip")) c.UseC(xlog.UserAgentHandler("user_agent")) c.UseC(xlog.RefererHandler("referer")) c.UseC(xlog.RequestIDHandler("req_id", "Request-Id")) }
func main() { if !checkFlags() { os.Exit(1) } sessionStore := sessions.NewCookieStore([]byte(*sessionHashKey), []byte(*sessionBlockKey)) // OpenID Connect Providers // Google oidcGoogleLoginRoute := "/logingoogle" oidcGoogleCBRoute := "/gcallback" oidcGoogle := &oidc.Google{ ClientID: *oidcGoogleClientID, ClientSecret: *oidcGoogleClientSecret, RedirectURI: *publicURL + oidcGoogleCBRoute, SessionStore: sessionStore, } // PayPal oidcPaypalLoginRoute := "/loginpaypal" oidcPaypalCBRoute := "/pcallback" oidcPaypal := &oidc.Paypal{ ClientID: *oidcPaypalClientID, ClientSecret: *oidcPaypalClientSecret, RedirectURI: *publicURL + oidcPaypalCBRoute, SessionStore: sessionStore, } // Dynamodb cfg := &aws.Config{} if *dynamodbEndpoint != "" { cfg.Endpoint = aws.String(*dynamodbEndpoint) } sess := session.New(cfg) if *debug { sess.Config.LogLevel = aws.LogLevel(aws.LogDebug) } // Model var m model.Model m = awsdynamo.NewModelFromSession(sess) // Controller // OAuth / OpenID Connect authCGoogle := controller.NewAuthController(m.UserPeer(), oidcGoogle, "google") authCPaypal := controller.NewAuthController(m.UserPeer(), oidcPaypal, "paypal") // Post Controller postContrData := &postDataProvider{ PostPeer: m.PostPeer(), UserPeer: m.UserPeer(), } postController := &controller.PostController{ Model: postContrData, } // Middleware baseChain := xhandler.Chain{} baseChain.UseC(xhandler.TimeoutHandler(2 * time.Second)) // Session management sessionMiddleware := middleware.Session{} sessionMiddleware.Init([]byte(*sessionHashKey), []byte(*sessionBlockKey)) baseChain.UseC(sessionMiddleware.Enable("posty-session")) // Chain for authenticated routes authedChain := xhandler.Chain{} authedChain = append(authedChain, baseChain...) authedChain.UseC(middleware.AuthenticatedFilter("/login")) authedChain.UseC(middleware.UserContext()) // Chain for authenticated routes with json response jsonChain := xhandler.Chain{} jsonChain = append(jsonChain, authedChain...) jsonChain.UseC(middleware.JSONWrapper()) // Chain for unauthenticated routes unauthedChain := xhandler.Chain{} unauthedChain = append(unauthedChain, baseChain...) unauthedChain.UseC(middleware.UnauthenticatedFilter("/")) // Main Context ctx := context.Background() route := func(chain xhandler.Chain, handler xhandler.HandlerC) web.Handler { return handle(ctx, chain.HandlerC(handler)) } // Routes mux := web.New() mux.Get("/api/posts", route(jsonChain, xhandler.HandlerFuncC(postController.Posts))) mux.Post("/api/posts", route(jsonChain, xhandler.HandlerFuncC(postController.Create))) mux.Delete("/api/posts/:id", route(jsonChain, xhandler.HandlerFuncC(postController.Remove))) // OIDC Routes mux.Get(oidcGoogleLoginRoute, route(unauthedChain, authCGoogle.Login())) mux.Get(oidcGoogleCBRoute, route(unauthedChain, authCGoogle.Callback("/"))) mux.Get(oidcPaypalLoginRoute, route(unauthedChain, authCPaypal.Login())) mux.Get(oidcPaypalCBRoute, route(unauthedChain, authCPaypal.Callback("/"))) mux.Get("/logout", route(authedChain, authCGoogle.Logout("/login"))) // Static file mux.Get("/login", route(unauthedChain, serveSingleFile(filepath.Join(*frontendPath, "login.html")))) mux.Get("/", route(authedChain, serveSingleFile(filepath.Join(*frontendPath, "index.html")))) mux.Get("/static/*", route(baseChain, serveFiles(filepath.Join(*frontendPath, "/static"), "/static/"))) log.Infof("Listening on %s", *listen) log.Fatal(http.ListenAndServe(":8080", gctx.ClearHandler(mux))) }
func construct(o *webOptions) xhandler.HandlerC { var chain xhandler.Chain chain.UseC(xhandler.CloseHandler) chain.UseC(xhandler.TimeoutHandler(o.reqTimeout)) chain.UseC(o.componentSetter) chain.UseC(o.templateCtxSetter) for _, m := range o.middlewares { chain.UseC(m) } chain.UseC(CompileInContext) chain.UseC(RenderInContext) if o.alwaysHTML { return chain.HandlerCF(WriteRenderedHTML) } return chain.HandlerCF(WriteRendered) }
func Example() { var ( // Define a user resource schema user = schema.Schema{ "id": schema.Field{ 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": schema.Field{ Required: true, ReadOnly: true, Filterable: true, Sortable: true, OnInit: &schema.Now, Validator: &schema.Time{}, }, "updated": schema.Field{ 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": schema.Field{ Required: true, Filterable: true, Validator: &schema.String{ MaxLen: 150, }, }, } // Define a post resource schema post = schema.Schema{ // 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": schema.Field{ Required: true, Filterable: true, Validator: &schema.Reference{ Path: "users", }, }, "public": schema.Field{ Filterable: true, Validator: &schema.Bool{}, }, // Sub-documents are handled via a sub-schema "meta": schema.Field{ Schema: &schema.Schema{ "title": schema.Field{ Required: true, Validator: &schema.String{ MaxLen: 150, }, }, "body": schema.Field{ 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", resource.New(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", resource.New(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 a xhandler chain (see https://github.com/rs/xhandler) c := xhandler.Chain{} // Add close notifier handler so context is cancelled when the client closes // the connection c.UseC(xhandler.CloseHandler) // Add timeout handler c.UseC(xhandler.TimeoutHandler(2 * time.Second)) // Install a logger (see https://github.com/rs/xlog) c.UseC(xlog.NewHandler(xlog.Config{})) // Log API access c.UseC(xaccess.NewHandler()) // Add CORS support with passthrough option on so rest-layer can still // handle OPTIONS method c.UseC(cors.New(cors.Options{OptionsPassthrough: true}).HandlerC) // Bind the API under /api/ path http.Handle("/api/", http.StripPrefix("/api/", c.Handler(api))) // Serve it log.Print("Serving API on http://localhost:8080") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
func main() { auth.Config(*domain, *port, *client, *secret) tpl := templates.New("templates") // chain authenticated middleware c := xhandler.Chain{} c.UseC(func(next xhandler.HandlerC) xhandler.HandlerC { return auth.NewMiddleware(next) }) // server static assets files fs := http.FileServer(http.Dir("assets")) http.Handle("/assets/", http.StripPrefix("/assets/", fs)) http.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") token, err := auth.GetToken(code) if err != nil { http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } else { id, err := db.CreateUser(token) if err != nil { tpl.Error(w, err) } else { auth.SaveSession(w, id) http.Redirect(w, r, "/entries/new", http.StatusFound) } } }) http.Handle("/entries/", c.Handler( xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { u := ctx.Value("user").(*user.User) switch r.Method { case "GET": switch r.URL.Path[len("/entries/"):] { case "": entries, err := db.FindEntries(u.ID, 200) if err != nil { tpl.Error(w, err) } else { tpl.Render(w, "entries", entries) } case "new": tpl.Render(w, "new_entry", u) default: tpl.NotFound(w) } case "POST": rate := r.FormValue("rate") desc := r.FormValue("description") _, err := db.CreateEntry(u.ID, rate, desc) if err != nil { tpl.Error(w, err) } else { http.Redirect(w, r, "/entries", http.StatusFound) } default: http.Error(w, "", http.StatusMethodNotAllowed) } }))) http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) { auth.DestroySession(w) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) }) http.HandleFunc("/about", func(w http.ResponseWriter, r *http.Request) { tpl.Render(w, "about", nil) }) http.HandleFunc("/demo", func(w http.ResponseWriter, r *http.Request) { auth.SaveSession(w, demo_id) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) }) http.Handle("/stats/", c.Handler( xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { u := ctx.Value("user").(*user.User) switch r.Method { case "GET": entries, err := db.FindEntries(u.ID, 10) if err == nil { p := struct { Distribution map[string]int Rate entry.RateStats }{ entry.FeelingsDistribution(entries), entry.RateByDay(entries), } if len(entries) == 0 { tpl.Render(w, "entries", entries) return } json, err := json.Marshal(p) if err == nil { tpl.Render(w, "stats", template.JS(json)) return } } tpl.Error(w, err) default: http.Error(w, "", http.StatusMethodNotAllowed) } }))) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { if r.URL.Path != "/" { tpl.NotFound(w) return } _, err := auth.CurrenUser(r) if err == nil { http.Redirect(w, r, "/entries/new", 302) } else { p := struct { FacebookURL string }{ auth.RedirectURL(), } tpl.Render(w, "index", p) } } else { http.Error(w, "", http.StatusMethodNotAllowed) } }) log.Fatal(http.ListenAndServe(":"+*port, nil)) }