diff --git a/server/api/api.go b/server/api/api.go new file mode 100644 index 00000000..0ed0f150 --- /dev/null +++ b/server/api/api.go @@ -0,0 +1,27 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "github.com/labstack/echo" +) + +func code(code int) *echo.HTTPError { + return echo.NewHTTPError(code, "") +} + +func sock(c *echo.Context) bool { + return c.Request().Header.Get(echo.Upgrade) == echo.WebSocket +} diff --git a/server/api/auth.go b/server/api/auth.go new file mode 100644 index 00000000..5acd4fa4 --- /dev/null +++ b/server/api/auth.go @@ -0,0 +1,32 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "github.com/labstack/echo" +) + +// AuthOpts defines options for the Head middleward +type AuthOpts struct { +} + +// Auth defines middleware for reading JWT authentication tokens +func Auth(opts *AuthOpts) echo.MiddlewareFunc { + return func(h echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + return h(c) + } + } +} diff --git a/server/api/cors.go b/server/api/cors.go new file mode 100644 index 00000000..2d59a775 --- /dev/null +++ b/server/api/cors.go @@ -0,0 +1,84 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "net/http" + "strconv" + "strings" + + "github.com/labstack/echo" +) + +// CorsOpts defines options for the Head middleward +type CorsOpts struct { + AllowedMethods []string + AllowedHeaders []string + AccessControlMaxAge int +} + +// Cors defines middleware for specifying CORS headers +func Cors(opts *CorsOpts) echo.MiddlewareFunc { + return func(h echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + + origin := c.Request().Header.Get("Origin") + + if origin == "" { + return h(c) + } + + // Set default values for opts.AllowedMethods + allowedMethods := opts.AllowedMethods + if len(allowedMethods) == 0 { + allowedMethods = []string{"GET", "PUT", "POST", "PATCH", "TRACE", "DELETE"} + } + + // Set default values for opts.AllowedHeaders + allowedHeaders := opts.AllowedHeaders + if len(allowedHeaders) == 0 { + allowedHeaders = []string{"Accept", "Content-Type", "Origin"} + } + + // Set default values for opts.AccessControlMaxAge + accessControlMaxAge := opts.AccessControlMaxAge + if accessControlMaxAge == 0 { + accessControlMaxAge = 3600 + } + + // Normalize AllowedMethods and make comma-separated-values + normedMethods := []string{} + for _, allowedMethod := range allowedMethods { + normed := http.CanonicalHeaderKey(allowedMethod) + normedMethods = append(normedMethods, normed) + } + + // Normalize AllowedHeaders and make comma-separated-values + normedHeaders := []string{} + for _, allowedHeader := range allowedHeaders { + normed := http.CanonicalHeaderKey(allowedHeader) + normedHeaders = append(normedHeaders, normed) + } + + c.Response().Header().Set("Access-Control-Allow-Methods", strings.Join(normedMethods, ",")) + c.Response().Header().Set("Access-Control-Allow-Headers", strings.Join(normedHeaders, ",")) + c.Response().Header().Set("Access-Control-Max-Age", strconv.Itoa(accessControlMaxAge)) + c.Response().Header().Set("Access-Control-Allow-Origin", origin) + + return h(c) + + } + } +} diff --git a/server/api/head.go b/server/api/head.go new file mode 100644 index 00000000..94aeafcc --- /dev/null +++ b/server/api/head.go @@ -0,0 +1,43 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "github.com/labstack/echo" +) + +// HeadOpts defines options for the Head middleward +type HeadOpts struct { + CacheControl string +} + +// Head defines middleware for setting miscellaneous headers +func Head(opts *HeadOpts) echo.MiddlewareFunc { + return func(h echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + + // Set default values for opts.PoweredBy + cacheControl := opts.CacheControl + if cacheControl == "" { + cacheControl = "no-cache" + } + + c.Response().Header().Set("Cache-control", cacheControl) + + return h(c) + + } + } +} diff --git a/server/api/info.go b/server/api/info.go new file mode 100644 index 00000000..7dd67348 --- /dev/null +++ b/server/api/info.go @@ -0,0 +1,43 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "github.com/labstack/echo" +) + +// InfoOpts defines options for the Head middleward +type InfoOpts struct { + PoweredBy string +} + +// Info defines middleware for specifying the server powered-by header +func Info(opts *InfoOpts) echo.MiddlewareFunc { + return func(h echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + + // Set default values for opts.PoweredBy + poweredBy := opts.PoweredBy + if poweredBy == "" { + poweredBy = "Surreal" + } + + c.Response().Header().Set("X-Powered-By", poweredBy) + + return h(c) + + } + } +} diff --git a/server/api/type.go b/server/api/type.go new file mode 100644 index 00000000..3ddd3e55 --- /dev/null +++ b/server/api/type.go @@ -0,0 +1,62 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "mime" + "sort" + + "github.com/labstack/echo" +) + +// TypeOpts defines options for the Head middleward +type TypeOpts struct { + AllowedContent []string +} + +// Type defines middleware for checking the request content-type +func Type(opts *TypeOpts) echo.MiddlewareFunc { + return func(h echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + + // Set default values for opts.AllowedContent + allowedContent := opts.AllowedContent + if len(allowedContent) == 0 { + allowedContent = []string{echo.ApplicationJSON} + } + + // Extract the Content-Type header + header := c.Request().Header.Get(echo.ContentType) + cotype, _, _ := mime.ParseMediaType(header) + + // Sort and search opts.AllowedContent types + sort.Strings(allowedContent) + i := sort.SearchStrings(allowedContent, cotype) + + if c.Request().ContentLength == 0 { + return h(c) + } + + if c.Request().ContentLength >= 1 { + if i < len(allowedContent) { + return h(c) + } + } + + return code(415) + + } + } +} diff --git a/server/crud.go b/server/crud.go new file mode 100644 index 00000000..4b8d3afd --- /dev/null +++ b/server/crud.go @@ -0,0 +1,36 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "github.com/abcum/surreal/sql" + "github.com/labstack/echo" +) + +func crud(c *echo.Context) error { + + s, e := sql.NewParser(c.Request().Body).Parse() + + if e == nil { + return c.JSON(200, show(s)) + } + + if e != nil { + return c.JSON(422, oops(e)) + } + + return nil + +} diff --git a/server/errors.go b/server/errors.go new file mode 100644 index 00000000..88da9645 --- /dev/null +++ b/server/errors.go @@ -0,0 +1,104 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "github.com/labstack/echo" +) + +func show(i interface{}) interface{} { + return i +} + +func oops(e error) interface{} { + return map[string]interface{}{ + "code": 422, + "details": "Request problems detected", + "documentation": docs, + "information": e.Error(), + } +} + +func errors(err error, c *echo.Context) { + + code := 500 + + if e, ok := err.(*echo.HTTPError); ok { + code = e.Code() + } + + c.JSON(code, errs[code]) + +} + +const docs = "https://docs.surreal.io/" + +var errs = map[int]interface{}{ + + 200: map[string]interface{}{ + "code": 200, + "details": "Information", + "documentation": docs, + "information": "Visit the documentation for details on accessing the api.", + }, + + 401: map[string]interface{}{ + "code": 401, + "details": "Authentication failed", + "documentation": docs, + "information": "Your authentication details are invalid. Reauthenticate using a valid token.", + }, + + 403: map[string]interface{}{ + "code": 403, + "details": "Request resource forbidden", + "documentation": docs, + "information": "Your request was forbidden. Perhaps you don't have the necessary permissions to access this resource.", + }, + + 404: map[string]interface{}{ + "code": 404, + "details": "Request resource not found", + "documentation": docs, + "information": "The requested resource does not exist. Check that you have entered the url correctly.", + }, + + 405: map[string]interface{}{ + "code": 405, + "details": "This method is not allowed", + "documentation": docs, + "information": "The requested http method is not allowed for this resource. Refer to the documentation for allowed methods.", + }, + + 415: map[string]interface{}{ + "code": 415, + "details": "Unsupported content type requested", + "documentation": docs, + "information": "Requests to the api must use the 'Content-Type: application/json' header. Check your request settings and try again.", + }, + + 422: map[string]interface{}{ + "code": 422, + "details": "Request problems detected", + "documentation": docs, + "information": "There is a problem with your request. The request needs to adhere to certain constraints.", + }, + + 500: map[string]interface{}{ + "code": 500, + "details": "There was a problem with our servers, and we have been notified", + "documentation": docs, + }, +} diff --git a/server/json.go b/server/json.go new file mode 100644 index 00000000..47620373 --- /dev/null +++ b/server/json.go @@ -0,0 +1,30 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "encoding/json" +) + +func encode(i interface{}) interface{} { + obj, _ := json.Marshal(i) + return string(obj) +} + +func decode(i interface{}) interface{} { + var obj map[string]interface{} + json.Unmarshal([]byte(i.(string)), &obj) + return obj +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 00000000..147a1850 --- /dev/null +++ b/server/server.go @@ -0,0 +1,94 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "log" + "sync" + + "github.com/abcum/surreal/cnf" + "github.com/abcum/surreal/server/api" + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" +) + +// Setup sets up the server for rest and websockets +func Setup(ctx cnf.Context) (e error) { + + var wg sync.WaitGroup + + wg.Add(2) + + // ------------------------------------------------------- + // REST handler + // ------------------------------------------------------- + + r := echo.New() + + r.Any("/", crud) + + r.SetDebug(ctx.Verbose) + r.AutoIndex(false) + r.SetHTTPErrorHandler(errors) + + r.Use(middleware.Gzip()) + r.Use(middleware.Logger()) + r.Use(middleware.Recover()) + r.Use(api.Head(&api.HeadOpts{})) + r.Use(api.Type(&api.TypeOpts{})) + r.Use(api.Cors(&api.CorsOpts{})) + r.Use(api.Auth(&api.AuthOpts{})) + + go func() { + defer wg.Done() + log.Printf("Starting HTTP server on %s", ctx.Http) + r.Run(ctx.Http) + }() + + // ------------------------------------------------------- + // SOCK handler + // ------------------------------------------------------- + + s := echo.New() + + s.WebSocket("/", sock) + + r.SetDebug(ctx.Verbose) + s.AutoIndex(false) + s.SetHTTPErrorHandler(errors) + + s.Use(middleware.Gzip()) + s.Use(middleware.Logger()) + s.Use(middleware.Recover()) + s.Use(api.Head(&api.HeadOpts{})) + s.Use(api.Type(&api.TypeOpts{})) + s.Use(api.Cors(&api.CorsOpts{})) + s.Use(api.Auth(&api.AuthOpts{})) + + go func() { + defer wg.Done() + log.Printf("Starting SOCK server on %s", ctx.Sock) + s.Run(ctx.Sock) + }() + + // ------------------------------------------------------- + // Start server + // ------------------------------------------------------- + + wg.Wait() + + return nil + +} diff --git a/server/sock.go b/server/sock.go new file mode 100644 index 00000000..ab50b38e --- /dev/null +++ b/server/sock.go @@ -0,0 +1,53 @@ +// Copyright © 2016 Abcum Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "github.com/abcum/surreal/sql" + "github.com/labstack/echo" + "golang.org/x/net/websocket" +) + +func sock(c *echo.Context) error { + + ws := c.Socket() + + var msg string + + for { + + if err := websocket.Message.Receive(ws, &msg); err != nil { + break + } + + s, e := sql.Parse(msg) + + if e == nil { + if err := websocket.Message.Send(ws, encode(show(s))); err != nil { + break + } + } + + if e != nil { + if err := websocket.Message.Send(ws, encode(oops(e))); err != nil { + break + } + } + + } + + return nil + +}