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 Example() { session, err := mgo.Dial("") if err != nil { log.Fatalf("Can't connect to MongoDB: %s", err) } db := "test_rest_layer" index := resource.NewIndex() users := index.Bind("users", user, mongo.NewHandler(session, db, "users"), resource.Conf{ AllowedModes: resource.ReadWrite, }) users.Bind("posts", "user", post, mongo.NewHandler(session, db, "posts"), resource.Conf{ AllowedModes: resource.ReadWrite, }) api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } http.Handle("/", cors.New(cors.Options{OptionsPassthrough: true}).Handler(api)) 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", resource.New(models.UserSchema, mem.NewHandler(), resource.Conf{ AllowedModes: resource.ReadWrite, PaginationDefaultLimit: 50, })) models.SetAuthUserResource(users) index.Bind("auth", resource.New(models.AuthSchema, mem.NewHandler(), resource.Conf{ AllowedModes: []resource.Mode{resource.Create}, })) api, err := rest.NewHandler(index) if err != nil { log.Fatal(err) } http.Handle("/", api) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
func ExampleMiddleware() { index := resource.NewIndex() api, _ := rest.NewHandler(index) // Add a very basic auth using a middleware api.Use(&AuthMiddleware{ Authenticator: func(user string, password string) bool { // code to check credentials return true }, }) }
func main() { index := resource.NewIndex() // configure your resources api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } handler := cors.Default().Handler(api) log.Fatal(http.ListenAndServe(":8080", handler)) }
func ExampleIf() { index := resource.NewIndex() api, _ := rest.NewHandler(index) api.Use(rest.If{ Condition: func(ctx context.Context, r *http.Request) bool { route, ok := rest.RouteFromContext(ctx) // True if current resource endpoint is users return ok && route.ResourcePath.Path() == "users" }, Then: &SomeMiddleware{}, }) }
func main() { // Create a REST API resource index index := resource.NewIndex() // Bind user on /users users := index.Bind("users", resource.New(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{ &resource.Item{ID: "admin", Updated: time.Now(), ETag: "abcd", Payload: map[string]interface{}{ "id": "admin", "name": "Dilbert", "password": secret, }}, &resource.Item{ID: "john", Updated: time.Now(), ETag: "efgh", Payload: map[string]interface{}{ "id": "john", "name": "John Doe", "password": secret, }}, }) // Bind post on /posts index.Bind("posts", resource.New(post, 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) } // Bind the authentication middleware // api.Use(myAuthMiddleware{userResource: users}) // Bind the API under / http.Handle("/", api) // Serve it log.Print("Serving API on http://localhost:8080") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
func ExampleNewMiddleware() { index := resource.NewIndex() api, _ := rest.NewHandler(index) // Add a very basic auth using a middleware api.Use(rest.NewMiddleware(func(ctx context.Context, r *http.Request, next rest.Next) (context.Context, int, http.Header, interface{}) { if u, p, ok := r.BasicAuth(); ok && validateCredentials(u, p) { // Store the authen user in the context ctx = context.WithValue(ctx, "user", u) // Pass to the next middleware return next(ctx) } // Stop the middleware chain and return a 401 HTTP error headers := http.Header{} headers.Set("WWW-Authenticate", "Basic realm=\"API\"") return ctx, 401, headers, &rest.Error{401, "Please provide proper credentials", nil} })) }
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) } // Add cors support h := cors.New(cors.Options{OptionsPassthrough: true}).Handler(api) // Bind the API under /api/ path http.Handle("/api/", http.StripPrefix("/api/", h)) // 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("posts", resource.New(post, mem.NewHandler(), resource.Conf{ // Posts can only be read, created and deleted, not updated 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) } // Bind the API under /api/ path http.Handle("/", 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() // configure your resources api, err := rest.NewHandler(index) if err != nil { log.Fatalf("Invalid API configuration: %s", err) } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fn := r.URL.Query().Get("callback") if fn != "" { w.Header().Set("Content-Type", "application/javascript") w.Write([]byte(";fn(")) } api.ServeHTTP(w, r) if fn != "" { w.Write([]byte(");")) } }) log.Fatal(http.ListenAndServe(":8080", handler)) }
func ExampleResponseSender() { index := resource.NewIndex() api, _ := rest.NewHandler(index) api.ResponseFormatter = myResponseFormatter{} }
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() { 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 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) } // Add cors support h := cors.New(cors.Options{OptionsPassthrough: true}).Handler(api) // Bind the API under /api/ path http.Handle("/api/", http.StripPrefix("/api/", h)) // 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("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 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 TestHandler(t *testing.T) { 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, }) // 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": "******"}`}, } api, err := rest.NewHandler(index) 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) } } gql, err := NewHandler(index) assert.NoError(t, err) r, _ := http.NewRequest("GET", "/?query={postsList{id,thumb_s_url:thumbnail_url(height:80)}}", nil) s, b := performRequest(gql, r) assert.Equal(t, 200, s) assert.Equal(t, "{\"data\":{\"postsList\":[{\"id\":\"ar5qrgukj5l7a6eq2ps0\",\"thumb_s_url\":\"http://dom.com/image.png?y=80\"}]}}\n", b) r, _ = http.NewRequest("GET", "/?query={postsList{i:id,m:meta{t:title, b:body},thumb_small_url:thumbnail_url(height:80)}}", nil) s, b = performRequest(gql, r) assert.Equal(t, 200, s) assert.Equal(t, "{\"data\":{\"postsList\":[{\"i\":\"ar5qrgukj5l7a6eq2ps0\",\"m\":{\"b\":\"This is my first post\",\"t\":\"First Post\"},\"thumb_small_url\":\"http://dom.com/image.png?y=80\"}]}}\n", b) r, _ = http.NewRequest("GET", "/?query={postsList{id,meta{title},user{id,name}}}", nil) s, b = performRequest(gql, r) assert.Equal(t, 200, s) assert.Equal(t, "{\"data\":{\"postsList\":[{\"id\":\"ar5qrgukj5l7a6eq2ps0\",\"meta\":{\"title\":\"First Post\"},\"user\":{\"id\":\"johndoe\",\"name\":\"John Doe\"}}]}}\n", b) r, _ = http.NewRequest("GET", "/?query={posts(id:\"ar5qrgukj5l7a6eq2ps0\"){followers{post{id,meta{title}},user{id,name}}}}", nil) s, b = performRequest(gql, r) assert.Equal(t, 200, s) assert.Equal(t, "{\"data\":{\"posts\":{\"followers\":[{\"post\":{\"id\":\"ar5qrgukj5l7a6eq2ps0\",\"meta\":{\"title\":\"First Post\"}},\"user\":{\"id\":\"fan1\",\"name\":\"Fan 1\"}},{\"post\":{\"id\":\"ar5qrgukj5l7a6eq2ps0\",\"meta\":{\"title\":\"First Post\"}},\"user\":{\"id\":\"fan2\",\"name\":\"Fan 2\"}},{\"post\":{\"id\":\"ar5qrgukj5l7a6eq2ps0\",\"meta\":{\"title\":\"First Post\"}},\"user\":{\"id\":\"fan3\",\"name\":\"Fan 3\"}}]}}}\n", b) r, _ = http.NewRequest("GET", "/?query={posts(id:\"ar5qrgukj5l7a6eq2ps0\"){id,meta{title},followers(limit:2){user{id,name}}}}", nil) s, b = performRequest(gql, r) assert.Equal(t, 200, s) assert.Equal(t, "{\"data\":{\"posts\":{\"followers\":[{\"user\":{\"id\":\"fan1\",\"name\":\"Fan 1\"}},{\"user\":{\"id\":\"fan2\",\"name\":\"Fan 2\"}},{\"user\":{\"id\":\"fan3\",\"name\":\"Fan 3\"}}],\"id\":\"ar5qrgukj5l7a6eq2ps0\",\"meta\":{\"title\":\"First Post\"}}}}\n", b) r, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{postsList{id,thumb_s_url:thumbnail_url(height:80)}}")) s, b = performRequest(gql, r) assert.Equal(t, 200, s) assert.Equal(t, "{\"data\":{\"postsList\":[{\"id\":\"ar5qrgukj5l7a6eq2ps0\",\"thumb_s_url\":\"http://dom.com/image.png?y=80\"}]}}\n", b) r, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"query\":\"{postsList{id,thumb_s_url:thumbnail_url(height:80)}}\"}")) r.Header.Set("Content-Type", "application/json") s, b = performRequest(gql, r) assert.Equal(t, 200, s) assert.Equal(t, "{\"data\":{\"postsList\":[{\"id\":\"ar5qrgukj5l7a6eq2ps0\",\"thumb_s_url\":\"http://dom.com/image.png?y=80\"}]}}\n", b) r, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{invalid json")) r.Header.Set("Content-Type", "application/json") s, b = performRequest(gql, r) assert.Equal(t, 400, s) assert.Equal(t, "Cannot unmarshal JSON: invalid character 'i' looking for beginning of object key string\n{\"data\":null,\"errors\":[{\"message\":\"Must provide an operation.\",\"locations\":[]}]}\n", b) r, _ = http.NewRequest("PUT", "/", nil) s, b = performRequest(gql, r) assert.Equal(t, 405, s) assert.Equal(t, "Method Not Allowed\n", b) }
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) } }