Skip to content

Commit 09c64e6

Browse files
committed
Working on POC
1 parent f6628b9 commit 09c64e6

File tree

6 files changed

+325
-1
lines changed

6 files changed

+325
-1
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@
1010

1111
# Output of the go coverage tool, specifically when used with LiteIDE
1212
*.out
13+
14+
# editors
15+
.idea
16+
.DS_Store
17+
.vscode

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ REST API framework for go lang
33

44
# Framework is under development
55
## Status:
6-
- Working on POC as per concept
6+
- Working on POC
7+
- Request Interceptors/Middlewares
8+
- Routes with URL pattern
9+
- Methods [GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH]
10+
- Extend routes with namespace
11+
- Error handler
12+
- HTTP, HTTPS support
13+
714
```
815
var api rest.API
916

api.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*!
2+
* utils-api-framework
3+
* Copyright(c) 2019 Roshan Gade
4+
* MIT Licensed
5+
*/
6+
package rest
7+
8+
import (
9+
"./utils"
10+
"fmt"
11+
"net/http"
12+
"regexp"
13+
)
14+
15+
type API struct {
16+
routes []route
17+
interceptors []interceptor
18+
errors []errorHandler
19+
}
20+
21+
type route struct {
22+
method string
23+
pattern string
24+
regex *regexp.Regexp
25+
params []string
26+
handle func(ctx *Context)
27+
}
28+
29+
type interceptor struct {
30+
handle func(ctx *Context)
31+
}
32+
33+
//TODO: check errors in language specifications
34+
type errorHandler struct {
35+
code string
36+
handle func(ctx *Context)
37+
}
38+
39+
func (api *API) handler(method string, pattern string, handle func(ctx *Context)) {
40+
regex, params, err := utils.Compile(pattern)
41+
if err != nil {
42+
fmt.Println("Error in pattern", err)
43+
panic(1)
44+
}
45+
api.routes = append(api.routes, route{
46+
method: method,
47+
pattern: pattern,
48+
regex: regex,
49+
params: params,
50+
handle: handle,
51+
})
52+
}
53+
54+
func (api API) ServeHTTP(res http.ResponseWriter, req *http.Request) {
55+
//fmt.Println("-------------------------------------")
56+
//fmt.Println("URI: ", req.RequestURI)
57+
//fmt.Println("Method: ", req.Method)
58+
//fmt.Println("Form: ", req.Form)
59+
//fmt.Println("RawPath: ", req.URL.RawPath)
60+
//fmt.Println("Header: ", req.Header)
61+
//fmt.Println("Path: ", req.URL.Path)
62+
//fmt.Println("RawQuery: ", req.URL.RawQuery)
63+
//fmt.Println("Opaque: ", req.URL.Opaque)
64+
//fmt.Println("-------------------------------------")
65+
urlPath := []byte(req.URL.Path)
66+
67+
ctx := Context{
68+
req: req,
69+
res: res,
70+
}
71+
72+
ctx.init()
73+
74+
for _, route := range api.interceptors {
75+
if ctx.err != nil {
76+
break
77+
}
78+
79+
if ctx.end == true {
80+
break
81+
}
82+
83+
route.handle(&ctx)
84+
}
85+
86+
for _, route := range api.routes {
87+
if ctx.err != nil {
88+
break
89+
}
90+
91+
if ctx.end == true {
92+
break
93+
}
94+
95+
if route.method == req.Method && route.regex.Match(urlPath) {
96+
ctx.Params = utils.Exec(route.regex, route.params, urlPath)
97+
route.handle(&ctx)
98+
}
99+
}
100+
101+
//TODO: NOT FOUND, INTERNAL SERVER ERROR, ETC
102+
103+
for _, route := range api.errors {
104+
if ctx.end == true {
105+
break
106+
}
107+
108+
if ctx.err != nil && route.code == ctx.err.Error() {
109+
route.handle(&ctx)
110+
}
111+
}
112+
}
113+
114+
func (api *API) Use(handle func(ctx *Context)) {
115+
api.interceptors = append(api.interceptors, interceptor{handle: handle})
116+
}
117+
118+
func (api *API) GET(pattern string, handle func(ctx *Context)) {
119+
api.handler("GET", pattern, handle)
120+
}
121+
122+
//TODO: POST, PUT, DELETE, OPTIONS, HEAD, PATCH
123+
124+
func (api *API) Error(code string, handle func(ctx *Context)) {
125+
api.errors = append(api.errors, errorHandler{
126+
code: code,
127+
handle: handle,
128+
})
129+
}

context.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*!
2+
* utils-api-framework
3+
* Copyright(c) 2019 Roshan Gade
4+
* MIT Licensed
5+
*/
6+
package rest
7+
8+
import (
9+
"encoding/json"
10+
"errors"
11+
"net/http"
12+
)
13+
14+
type Context struct {
15+
req *http.Request
16+
res http.ResponseWriter
17+
Params *map[string]string
18+
data map[string]interface{}
19+
err error
20+
end bool
21+
}
22+
23+
func (ctx *Context) init() {
24+
ctx.data = make(map[string]interface{})
25+
//TODO: initialization
26+
}
27+
28+
func (ctx *Context) Set(key string, val interface{}) {
29+
ctx.data[key] = val
30+
}
31+
32+
func (ctx *Context) Get(key string) interface{} {
33+
return ctx.data[key]
34+
}
35+
36+
func (ctx *Context) Status(code int) *Context {
37+
ctx.res.WriteHeader(code)
38+
return ctx
39+
}
40+
41+
func (ctx *Context) SetHeader(key string, val string) *Context {
42+
ctx.res.Header().Set(key, val)
43+
return ctx
44+
}
45+
46+
func (ctx *Context) Throw(err error) {
47+
ctx.err = err
48+
}
49+
50+
func (ctx *Context) Write(data []byte) {
51+
if ctx.end == true {
52+
return
53+
}
54+
_, err := ctx.res.Write(data)
55+
if err != nil {
56+
ctx.err = errors.New("RESPONSE ERROR")
57+
return
58+
}
59+
60+
ctx.end = true
61+
}
62+
63+
func (ctx *Context) Send(data string) {
64+
ctx.Write([]byte(data))
65+
}
66+
67+
func (ctx *Context) SendJSON(data interface{}) {
68+
body, err := json.Marshal(data)
69+
if err != nil {
70+
ctx.err = errors.New("INVALID JSON")
71+
return
72+
}
73+
ctx.SetHeader("Content-Type", "application/json")
74+
ctx.Write(body)
75+
}

examples/server.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package main
2+
3+
import (
4+
".."
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
)
9+
10+
func main() {
11+
var api rest.API
12+
13+
// request interceptor / middleware
14+
api.Use(func(ctx *rest.Context) {
15+
ctx.Set("authtoken", "roshangade")
16+
})
17+
18+
// routes
19+
api.GET("/", func(ctx *rest.Context) {
20+
ctx.Send("Hello World!")
21+
})
22+
23+
api.GET("/foo", func(ctx *rest.Context) {
24+
ctx.Status(401).Throw(errors.New("UNAUTHORIZED"))
25+
})
26+
27+
api.GET("/:bar", func(ctx *rest.Context) {
28+
fmt.Println("authtoken", ctx.Get("authtoken"))
29+
ctx.SendJSON(ctx.Params)
30+
})
31+
32+
// error handler
33+
api.Error("UNAUTHORIZED", func(ctx *rest.Context) {
34+
ctx.Send("You are unauthorized")
35+
})
36+
37+
fmt.Println("Starting server.")
38+
39+
http.ListenAndServe(":8080", api)
40+
}

utils/url.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*!
2+
* utils-api-framework
3+
* Copyright(c) 2019 Roshan Gade
4+
* MIT Licensed
5+
*/
6+
package utils
7+
8+
import (
9+
"regexp"
10+
"strings"
11+
)
12+
13+
const sep = "/"
14+
15+
func Compile(str string) (*regexp.Regexp, []string, error) {
16+
pattern := ""
17+
keys := make([]string, 0)
18+
_str := strings.Split(str, "/")
19+
20+
for _, val := range _str {
21+
if val != "" {
22+
switch val[0] {
23+
case 42:
24+
pattern += "(?:/(.*))"
25+
keys = append(keys, "*")
26+
27+
case 58:
28+
length := len(val)
29+
lastChar := val[length-1]
30+
if lastChar == 63 {
31+
pattern += "(?:/([^/]+?))?"
32+
keys = append(keys, val[1:(length-1)])
33+
} else {
34+
pattern += sep + "([^/]+?)"
35+
keys = append(keys, val[1:])
36+
}
37+
38+
default:
39+
pattern += sep + val
40+
}
41+
}
42+
}
43+
44+
// if len(keys) == 0 {
45+
// pattern += "(?:/)?"
46+
// }
47+
48+
regex, err := regexp.Compile("^" + pattern + "/?$")
49+
50+
return regex, keys, err
51+
}
52+
53+
func Exec(regex *regexp.Regexp, keys []string, uri []byte) *map[string]string {
54+
params := make(map[string]string)
55+
56+
matches := regex.FindAllSubmatch(uri, -1)
57+
58+
for _, val := range matches {
59+
for i, k := range val[1:] {
60+
params[keys[i]] = string(k)
61+
if keys[i] == "*" {
62+
params["*"] = sep + params["*"]
63+
}
64+
}
65+
}
66+
67+
return &params
68+
}

0 commit comments

Comments
 (0)