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) } }
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 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) } }
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() { // 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) } }