├── app.go ├── middlewares ├── recovery │ └── recovery.go └── metrics │ └── metrics.go ├── standalone.go ├── appengine.go ├── context.go ├── log.go ├── module.go ├── response_test.go ├── README.md ├── response.go └── request.go /app.go: -------------------------------------------------------------------------------- 1 | package hikaru 2 | 3 | type Application struct { 4 | *Module 5 | } 6 | 7 | func New() *Application { 8 | return &Application{ 9 | Module: NewModule("/"), 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /middlewares/recovery/recovery.go: -------------------------------------------------------------------------------- 1 | package recovery 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/najeira/hikaru" 7 | ) 8 | 9 | func HandlerFunc(h hikaru.HandlerFunc) hikaru.HandlerFunc { 10 | return func(c *hikaru.Context) { 11 | defer func() { 12 | if err := recover(); err != nil { 13 | handlePanic(c, err) 14 | } 15 | }() 16 | 17 | // call handler 18 | h(c) 19 | } 20 | } 21 | 22 | func handlePanic(c *hikaru.Context, err interface{}) { 23 | buf := make([]byte, 4096) 24 | n := runtime.Stack(buf, false) 25 | c.Errorf("%s\n%s", err, string(buf[:n])) 26 | } 27 | -------------------------------------------------------------------------------- /standalone.go: -------------------------------------------------------------------------------- 1 | // +build !appengine 2 | 3 | package hikaru 4 | 5 | import ( 6 | "log" 7 | ) 8 | 9 | var logLevelNameMap = map[int]string{ 10 | LogTrace: "[TRACE]", 11 | LogDebug: "[DEBUG]", 12 | LogInfo: "[INFO] ", 13 | LogWarn: "[WARN] ", 14 | LogError: "[ERROR]", 15 | LogCritical: "[CRIT] ", 16 | } 17 | 18 | type defaultLogger struct { 19 | level int 20 | } 21 | 22 | func NewLogger(level int) Logger { 23 | return &defaultLogger{level: level} 24 | } 25 | 26 | func (l *defaultLogger) V(level int) bool { 27 | return l.level >= level && level > LogNo 28 | } 29 | 30 | func (l *defaultLogger) Printf(c *Context, level int, format string, args ...interface{}) { 31 | if l.V(level) { 32 | if name, ok := logLevelNameMap[level]; ok { 33 | log.Printf(name+format, args...) 34 | } else { 35 | log.Printf(format, args...) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /appengine.go: -------------------------------------------------------------------------------- 1 | // +build appengine 2 | 3 | package hikaru 4 | 5 | import ( 6 | "appengine" 7 | ) 8 | 9 | var logLevelAppEngineLoggerMap = map[int](func(appengine.Context, string, ...interface{})){ 10 | LogTrace: appengine.Context.Debugf, 11 | LogDebug: appengine.Context.Debugf, 12 | LogInfo: appengine.Context.Infof, 13 | LogWarn: appengine.Context.Warningf, 14 | LogError: appengine.Context.Errorf, 15 | LogCritical: appengine.Context.Criticalf, 16 | } 17 | 18 | type appengineLogger struct { 19 | level int 20 | } 21 | 22 | func NewLogger(level int) Logger { 23 | return &appengineLogger{level: level} 24 | } 25 | 26 | func (l *appengineLogger) V(level int) bool { 27 | return l.level >= level && level > LogNo 28 | } 29 | 30 | func (l *appengineLogger) Printf(c *Context, level int, format string, args ...interface{}) { 31 | if l.V(level) { 32 | if f, ok := logLevelAppEngineLoggerMap[level]; ok { 33 | f(appengine.NewContext(c.Request), format, args...) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package hikaru 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "net/url" 7 | "sync" 8 | 9 | "github.com/julienschmidt/httprouter" 10 | ) 11 | 12 | type Context struct { 13 | // request 14 | *http.Request 15 | params httprouter.Params 16 | query url.Values 17 | 18 | // response 19 | http.ResponseWriter 20 | status int 21 | size int 22 | } 23 | 24 | var ( 25 | ErrKeyNotExist = errors.New("not exist") 26 | contextPool sync.Pool 27 | ) 28 | 29 | // Returns the Context. 30 | func getContext(w http.ResponseWriter, r *http.Request, params httprouter.Params) *Context { 31 | var c *Context = nil 32 | if v := contextPool.Get(); v != nil { 33 | c = v.(*Context) 34 | } else { 35 | c = &Context{} 36 | } 37 | c.init(w, r, params) 38 | return c 39 | } 40 | 41 | // Release a Context. 42 | func releaseContext(c *Context) { 43 | c.init(nil, nil, nil) 44 | contextPool.Put(c) 45 | } 46 | 47 | func (c *Context) init(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 48 | c.Request = r 49 | c.params = params 50 | c.query = nil 51 | c.ResponseWriter = w 52 | c.status = http.StatusOK 53 | c.size = -1 54 | } 55 | -------------------------------------------------------------------------------- /middlewares/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "time" 5 | 6 | mt "github.com/najeira/goutils/metrics" 7 | "github.com/najeira/hikaru" 8 | ) 9 | 10 | var ( 11 | metricsHttp *mt.MetricsHttp 12 | ) 13 | 14 | func init() { 15 | metricsHttp = mt.NewMetricsHttp() 16 | go metricsUpdateClients() 17 | } 18 | 19 | func HandlerFunc(h hikaru.HandlerFunc) hikaru.HandlerFunc { 20 | return func(c *hikaru.Context) { 21 | metricsStart() 22 | defer metricsEnd(c, time.Now()) 23 | 24 | // call handler 25 | h(c) 26 | } 27 | } 28 | 29 | func metricsUpdateClients() { 30 | for range time.Tick(time.Second) { 31 | metricsHttp.UpdateClients() 32 | } 33 | } 34 | 35 | func metricsStart() { 36 | metricsHttp.IncClient() 37 | } 38 | 39 | func metricsEnd(c *hikaru.Context, start time.Time) { 40 | metricsHttp.DecClient() 41 | metricsHttp.Measure(time.Now().Sub(start)) 42 | 43 | sc := c.Status() 44 | if 200 <= sc && sc <= 299 { 45 | metricsHttp.Mark2xx() 46 | } else if 300 <= sc && sc <= 399 { 47 | metricsHttp.Mark3xx() 48 | } else if 400 <= sc && sc <= 499 { 49 | metricsHttp.Mark4xx() 50 | } else if 500 <= sc && sc <= 599 { 51 | metricsHttp.Mark5xx() 52 | } 53 | } 54 | 55 | func Metrics() map[string]float64 { 56 | return metricsHttp.Get() 57 | } 58 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package hikaru 2 | 3 | const ( 4 | LogNo = iota 5 | LogCritical 6 | LogError 7 | LogWarn 8 | LogInfo 9 | LogDebug 10 | LogTrace 11 | ) 12 | 13 | var ( 14 | applicationLogger Logger 15 | generalLogger Logger 16 | ) 17 | 18 | type Logger interface { 19 | V(int) bool 20 | Printf(c *Context, level int, format string, args ...interface{}) 21 | } 22 | 23 | func init() { 24 | SetLogger(NewLogger(LogInfo)) 25 | SetGeneralLogger(NewLogger(LogWarn)) 26 | } 27 | 28 | func SetLogger(logger Logger) { 29 | applicationLogger = logger 30 | } 31 | 32 | func SetGeneralLogger(logger Logger) { 33 | generalLogger = logger 34 | } 35 | 36 | func logGenf(c *Context, level int, format string, args ...interface{}) { 37 | if generalLogger != nil && generalLogger.V(level) { 38 | generalLogger.Printf(c, level, format, args...) 39 | } 40 | } 41 | 42 | func logAppf(c *Context, level int, format string, args ...interface{}) { 43 | if applicationLogger != nil && applicationLogger.V(level) { 44 | applicationLogger.Printf(c, level, format, args...) 45 | } 46 | } 47 | 48 | func (c *Context) tracef(format string, args ...interface{}) { 49 | logGenf(c, LogTrace, format, args...) 50 | } 51 | 52 | func (c *Context) debugf(format string, args ...interface{}) { 53 | logGenf(c, LogDebug, format, args...) 54 | } 55 | 56 | func (c *Context) infof(format string, args ...interface{}) { 57 | logGenf(c, LogInfo, format, args...) 58 | } 59 | 60 | func (c *Context) warningf(format string, args ...interface{}) { 61 | logGenf(c, LogWarn, format, args...) 62 | } 63 | 64 | func (c *Context) errorf(format string, args ...interface{}) { 65 | logGenf(c, LogError, format, args...) 66 | } 67 | 68 | func (c *Context) criticalf(format string, args ...interface{}) { 69 | logGenf(c, LogCritical, format, args...) 70 | } 71 | 72 | func (c *Context) Tracef(format string, args ...interface{}) { 73 | logAppf(c, LogTrace, format, args...) 74 | } 75 | 76 | func (c *Context) Debugf(format string, args ...interface{}) { 77 | logAppf(c, LogDebug, format, args...) 78 | } 79 | 80 | func (c *Context) Infof(format string, args ...interface{}) { 81 | logAppf(c, LogInfo, format, args...) 82 | } 83 | 84 | func (c *Context) Warningf(format string, args ...interface{}) { 85 | logAppf(c, LogWarn, format, args...) 86 | } 87 | 88 | func (c *Context) Errorf(format string, args ...interface{}) { 89 | logAppf(c, LogError, format, args...) 90 | } 91 | 92 | func (c *Context) Criticalf(format string, args ...interface{}) { 93 | logAppf(c, LogCritical, format, args...) 94 | } 95 | -------------------------------------------------------------------------------- /module.go: -------------------------------------------------------------------------------- 1 | package hikaru 2 | 3 | import ( 4 | "net/http" 5 | "path" 6 | 7 | "github.com/julienschmidt/httprouter" 8 | ) 9 | 10 | type HandlerFunc func(*Context) 11 | 12 | type Middleware func(HandlerFunc) HandlerFunc 13 | 14 | type Module struct { 15 | parent *Module 16 | prefix string 17 | router *httprouter.Router 18 | middleware Middleware 19 | } 20 | 21 | func NewModule(prefix string) *Module { 22 | return &Module{ 23 | parent: nil, 24 | prefix: prefix, 25 | router: httprouter.New(), 26 | middleware: nil, 27 | } 28 | } 29 | 30 | func (m *Module) ServeHTTP(w http.ResponseWriter, req *http.Request) { 31 | m.router.ServeHTTP(w, req) 32 | } 33 | 34 | func (m *Module) Module(component string, middleware Middleware) *Module { 35 | prefix := path.Join(m.prefix, component) 36 | return &Module{ 37 | parent: m, 38 | prefix: prefix, 39 | router: m.router, 40 | middleware: middleware, 41 | } 42 | } 43 | 44 | func (m *Module) Handle(method, p string, handler HandlerFunc) { 45 | if m.middleware != nil { 46 | handler = m.middleware(handler) 47 | } 48 | h := m.wrapHandlerFunc(handler) 49 | m.router.Handle(method, path.Join(m.prefix, p), h) 50 | } 51 | 52 | func (m *Module) wrapHandlerFunc(handler HandlerFunc) httprouter.Handle { 53 | return func(w http.ResponseWriter, r *http.Request, hp httprouter.Params) { 54 | c := getContext(w, r, hp) 55 | defer releaseContext(c) 56 | c.tracef("hikaru: Request is %v", c.Request) 57 | handler(c) 58 | } 59 | } 60 | 61 | // POST is a shortcut for Module.Handle("POST", p, handle) 62 | func (m *Module) POST(p string, handler HandlerFunc) { 63 | m.Handle("POST", p, handler) 64 | } 65 | 66 | // GET is a shortcut for Module.Handle("GET", p, handle) 67 | func (m *Module) GET(p string, handler HandlerFunc) { 68 | m.Handle("GET", p, handler) 69 | } 70 | 71 | // OPTIONS is a shortcut for Module.Handle("OPTIONS", p, handle) 72 | func (m *Module) OPTIONS(p string, handler HandlerFunc) { 73 | m.Handle("OPTIONS", p, handler) 74 | } 75 | 76 | // HEAD is a shortcut for Module.Handle("HEAD", p, handle) 77 | func (m *Module) HEAD(p string, handler HandlerFunc) { 78 | m.Handle("HEAD", p, handler) 79 | } 80 | 81 | func (m *Module) Static(p, root string) { 82 | p = path.Join(p, "*filepath") 83 | fileServer := http.FileServer(http.Dir(root)) 84 | m.GET(p, func(c *Context) { 85 | fp, err := c.TryString("filepath") 86 | if err != nil { 87 | c.Errorf(err.Error()) 88 | c.Fail() 89 | } else { 90 | original := c.Request.URL.Path 91 | c.Request.URL.Path = fp 92 | fileServer.ServeHTTP(c.ResponseWriter, c.Request) 93 | c.Request.URL.Path = original 94 | } 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /response_test.go: -------------------------------------------------------------------------------- 1 | package hikaru 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | ) 8 | 9 | func responseObjects() (*httptest.ResponseRecorder, *http.Request, *Context, error) { 10 | wr := httptest.NewRecorder() 11 | req, err := http.NewRequest("GET", "http://example.com/", nil) 12 | if err != nil { 13 | return wr, req, nil, err 14 | } 15 | res := &Context{} 16 | res.init(wr, req, nil) 17 | return wr, req, res, err 18 | } 19 | 20 | func TestResponse(t *testing.T) { 21 | wr, req, res, err := responseObjects() 22 | if err != nil { 23 | t.Error(err) 24 | } 25 | 26 | if res.size != -1 { 27 | t.Errorf("size invalid") 28 | } 29 | if res.Size() != -1 { 30 | t.Errorf("Size invalid") 31 | } 32 | if res.status != 200 { 33 | t.Errorf("status invalid") 34 | } 35 | if res.Status() != 200 { 36 | t.Errorf("Status invalid") 37 | } 38 | if res.ResponseWriter != wr { 39 | t.Errorf("ResponseWriter invalid") 40 | } 41 | if res.Request != req { 42 | t.Errorf("request invalid") 43 | } 44 | } 45 | 46 | func TestResponseWriteHeader(t *testing.T) { 47 | wr, _, res, err := responseObjects() 48 | if err != nil { 49 | t.Error(err) 50 | } 51 | 52 | res.WriteHeader(300) 53 | if res.Written() { 54 | t.Errorf("Written invalid") 55 | } 56 | if res.Status() != 300 { 57 | t.Errorf("Status invalid") 58 | } 59 | if wr.Code != 200 { 60 | t.Errorf("WriteHeader invalid") 61 | } 62 | 63 | res.WriteHeader(301) 64 | if res.Written() { 65 | t.Errorf("Written invalid") 66 | } 67 | if res.Status() != 301 { 68 | t.Errorf("Status invalid") 69 | } 70 | if wr.Code != 200 { 71 | t.Errorf("WriteHeader invalid") 72 | } 73 | } 74 | 75 | func TestResponseWrite(t *testing.T) { 76 | wr, _, res, err := responseObjects() 77 | if err != nil { 78 | t.Error(err) 79 | } 80 | 81 | n, err := res.Write([]byte("hoge")) 82 | if err != nil { 83 | t.Error(err) 84 | } 85 | if n != 4 { 86 | t.Errorf("Write invalid") 87 | } 88 | if res.Size() != 4 { 89 | t.Errorf("Size invalid %d", res.Size()) 90 | } 91 | if res.Status() != 200 { 92 | t.Errorf("Status invalid") 93 | } 94 | if wr.Code != 200 { 95 | t.Errorf("Code invalid") 96 | } 97 | if wr.Body.String() != "hoge" { 98 | t.Errorf("Body invalid") 99 | } 100 | 101 | n, err = res.Write([]byte(" fuga")) 102 | if err != nil { 103 | t.Error(err) 104 | } 105 | if n != 5 { 106 | t.Errorf("Write invalid") 107 | } 108 | if res.Size() != 9 { 109 | t.Errorf("Size invalid %d", res.Size()) 110 | } 111 | if wr.Body.String() != "hoge fuga" { 112 | t.Errorf("Body invalid") 113 | } 114 | } 115 | 116 | func TestResponseRedirect(t *testing.T) { 117 | wr, _, res, err := responseObjects() 118 | if err != nil { 119 | t.Error(err) 120 | } 121 | 122 | res.Redirect("/hoge", 301) 123 | if !res.Written() { 124 | t.Errorf("Redirect invalid") 125 | } 126 | if res.Status() != 301 { 127 | t.Errorf("Status invalid") 128 | } 129 | if wr.Code != 301 { 130 | t.Errorf("Code invalid") 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | Hikaru is a web framework for Go. 4 | It supports standalone and Google App Engine. 5 | 6 | *This is under construction.* 7 | Do not use for production services. 8 | 9 | 10 | # Getting started 11 | 12 | Hello World: 13 | 14 | ``` 15 | package main 16 | 17 | import "github.com/najeira/hikaru" 18 | 19 | func main() { 20 | app := hikaru.New() 21 | app.GET("/", func(c *hikaru.Context) { 22 | c.Text("Hello World") 23 | }) 24 | app.Run(":8080") 25 | } 26 | ``` 27 | 28 | 29 | # Routing 30 | 31 | Using GET, POST, HEAD and OPTIONS: 32 | 33 | ``` 34 | app.GET("/get", get) 35 | app.POST("/post", post) 36 | app.HEAD("/head", head) 37 | app.OPTIONS("/options", options) 38 | ``` 39 | 40 | Hikaru uses julienschmidt's httprouter internaly. 41 | 42 | See: https://github.com/julienschmidt/httprouter 43 | 44 | # Request 45 | 46 | ## Parameters 47 | 48 | Query parameters: 49 | 50 | ``` 51 | app.GET("/user", func(c *hikaru.Context)) { 52 | // String get "Gopher" when the query is /user?name=Gopher 53 | // Second argument of String() is failover that will return 54 | // when the name does not exists in the query 55 | name := c.String("name", "") 56 | c.Text("Hello " + name) 57 | }) 58 | ``` 59 | 60 | Getting parameters in path same as query: 61 | 62 | ``` 63 | app.GET("/user/:name", func(c *hikaru.Context)) { 64 | // String get "Gopher" when the path is /user/Gopher 65 | name := c.String("name", "") 66 | c.Text("Hello " + name) 67 | }) 68 | ``` 69 | 70 | Shorthand to get int and float parameters: 71 | 72 | ``` 73 | app.GET("/user/:id", func(c *hikaru.Context)) { 74 | // Returns 123 if /user/123 75 | id := c.Int("id", 0) // id is int64 76 | }) 77 | app.GET("/hoge/:score", func(c *hikaru.Context)) { 78 | // Returns 12.3 if /hoge/12.3 79 | score := c.Float("score", 0.0) // score is float64 80 | }) 81 | ``` 82 | 83 | # Response 84 | 85 | ## Writing response 86 | 87 | ``` 88 | // Text writes string 89 | c.Text("Hello world.") 90 | 91 | // Json marshals the object to json string and write 92 | c.Json(someObj) 93 | 94 | // Raw writes []byte 95 | c.Raw(body, "application/octet-stream") 96 | ``` 97 | 98 | ## Redirect 99 | 100 | ``` 101 | // RedirectFound sends 302 Found 102 | c.RedirectFound("/foo/bar") 103 | 104 | // RedirectFound sends 301 Moved Permanently 105 | c.RedirectMoved("/foo/bar") 106 | 107 | // Redirect sends location and code 108 | c.Redirect("/foo/bar", statusCode) 109 | ``` 110 | 111 | ## Error response 112 | 113 | ``` 114 | // 304 Not Modified 115 | c.NotModified() 116 | 117 | // 401 Unauthorized 118 | c.Unauthorized() 119 | 120 | // 403 Forbidden 121 | c.Forbidden() 122 | 123 | // HTTP 404 Not Found 124 | c.NotFound() 125 | 126 | // 500 Internal Server Error 127 | c.Fail(err) 128 | ``` 129 | 130 | You can send your body with error status: 131 | 132 | ``` 133 | c.NotFound() 134 | c.Json(errObj) 135 | ``` 136 | 137 | ## Headers 138 | 139 | ``` 140 | // Header gets the response headers. 141 | headers := c.Header() 142 | 143 | // SetHeader sets a header value to the response. 144 | c.SetHeader("Foo", "Bar") 145 | 146 | // SetHeader sets a header value to the response. 147 | c.SetHeader("Foo", "Bar") 148 | ``` 149 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package hikaru 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "io" 7 | "net" 8 | "net/http" 9 | ) 10 | 11 | var ( 12 | _ http.ResponseWriter = (*Context)(nil) 13 | _ http.Hijacker = (*Context)(nil) 14 | _ http.Flusher = (*Context)(nil) 15 | _ http.CloseNotifier = (*Context)(nil) 16 | ) 17 | 18 | // Returns response headers. 19 | func (c *Context) Header() http.Header { 20 | return c.ResponseWriter.Header() 21 | } 22 | 23 | // GetHeader gets a response header. 24 | func (c *Context) GetHeader(key string) string { 25 | return c.Header().Get(key) 26 | } 27 | 28 | // SetHeader sets a response header. 29 | func (c *Context) SetHeader(key, value string) { 30 | c.Header().Set(key, value) 31 | } 32 | 33 | // Adds a response header. 34 | func (c *Context) AddHeader(key, value string) { 35 | c.Header().Add(key, value) 36 | } 37 | 38 | // Adds a cookie header. 39 | func (c *Context) SetCookie(cookie *http.Cookie) { 40 | c.Header().Set("Set-Cookie", cookie.String()) 41 | } 42 | 43 | func (c *Context) SetContentType(value string) { 44 | c.Header().Set("Content-Type", value) 45 | } 46 | 47 | func (c *Context) Status() int { 48 | return c.status 49 | } 50 | 51 | func (c *Context) Size() int { 52 | return c.size 53 | } 54 | 55 | func (c *Context) Written() bool { 56 | return c.size >= 0 57 | } 58 | 59 | func (c *Context) WriteHeader(code int) { 60 | if code > 0 && c.status != code { 61 | c.status = code 62 | } 63 | } 64 | 65 | func (c *Context) WriteHeaderAndSend(code int) { 66 | c.WriteHeader(code) 67 | c.writeHeaderIfNotSent() 68 | } 69 | 70 | func (c *Context) writeHeaderIfNotSent() { 71 | if !c.Written() { 72 | c.size = 0 73 | if c.status > 0 { 74 | c.ResponseWriter.WriteHeader(c.status) 75 | } 76 | } 77 | } 78 | 79 | func (c *Context) Write(msg []byte) (int, error) { 80 | c.writeHeaderIfNotSent() 81 | n, err := c.ResponseWriter.Write(msg) 82 | c.size += n 83 | return n, err 84 | } 85 | 86 | // Writes bytes and content type. 87 | func (c *Context) WriteBody(body []byte, contentType string) (int, error) { 88 | if contentType != "" { 89 | c.SetContentType(contentType) 90 | } 91 | return c.Write(body) 92 | } 93 | 94 | // Writes a text string. 95 | // The content type should be "text/plain; charset=utf-8". 96 | func (c *Context) Text(body string) (int, error) { 97 | c.SetContentType("text/plain; charset=utf-8") 98 | return io.WriteString(c, body) 99 | } 100 | 101 | func (c *Context) Json(value interface{}) error { 102 | c.SetContentType("application/json; charset=utf-8") 103 | enc := json.NewEncoder(c) 104 | return enc.Encode(value) 105 | } 106 | 107 | // Sets response to HTTP 3xx. 108 | func (c *Context) Redirect(path string, code int) { 109 | c.SetHeader("Location", path) 110 | http.Redirect(c, c.Request, path, code) 111 | c.writeHeaderIfNotSent() 112 | } 113 | 114 | // Sets response to HTTP 304 Not Modified. 115 | func (c *Context) NotModified() { 116 | c.WriteHeaderAndSend(http.StatusNotModified) 117 | } 118 | 119 | // Sets response to HTTP 401 Unauthorized. 120 | func (c *Context) Unauthorized() { 121 | c.WriteHeaderAndSend(http.StatusUnauthorized) 122 | } 123 | 124 | // Sets response to HTTP 403 Forbidden. 125 | func (c *Context) Forbidden() { 126 | c.WriteHeaderAndSend(http.StatusForbidden) 127 | } 128 | 129 | // Sets response to HTTP 404 Not Found. 130 | func (c *Context) NotFound() { 131 | c.WriteHeaderAndSend(http.StatusNotFound) 132 | } 133 | 134 | // Sets response to HTTP 500 Internal Server Erroc. 135 | func (c *Context) Fail() { 136 | c.WriteHeaderAndSend(http.StatusInternalServerError) 137 | } 138 | 139 | // Hijack lets the caller take over the connection. 140 | func (c *Context) Hijack() (net.Conn, *bufio.ReadWriter, error) { 141 | if c.size < 0 { 142 | c.size = 0 143 | } 144 | return c.ResponseWriter.(http.Hijacker).Hijack() 145 | } 146 | 147 | // CloseNotify returns a channel that receives a single value 148 | // when the client connection has gone away. 149 | func (c *Context) CloseNotify() <-chan bool { 150 | return c.ResponseWriter.(http.CloseNotifier).CloseNotify() 151 | } 152 | 153 | // Flush sends any buffered data to the client. 154 | func (c *Context) Flush() { 155 | c.ResponseWriter.(http.Flusher).Flush() 156 | } 157 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package hikaru 2 | 3 | import ( 4 | "mime/multipart" 5 | "net/http" 6 | "net/url" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // RequestHeader gets a request header. 12 | func (c *Context) RequestHeader(key string) string { 13 | return c.Request.Header.Get(key) 14 | } 15 | 16 | // IsPost returns true if the request method is POST. 17 | func (c *Context) IsPost() bool { 18 | m := strings.ToUpper(c.Request.Method) 19 | return m == "POST" 20 | } 21 | 22 | // IsGet returns true if the request method is GET. 23 | func (c *Context) IsGet() bool { 24 | m := strings.ToUpper(c.Request.Method) 25 | return m == "GET" 26 | } 27 | 28 | // IsAjax returns true if the request is XMLHttpRequest. 29 | func (c *Context) IsAjax() bool { 30 | h := c.Request.Header.Get("X-Requested-With") 31 | return h == "XMLHttpRequest" 32 | } 33 | 34 | // IsSecure returns true if the request is scure. 35 | func (c *Context) IsSecure() bool { 36 | // TODO: HTTP_X_FORWARDED_SSL, HTTP_X_FORWARDED_SCHEME, HTTP_X_FORWARDED_PROTO 37 | return c.Request.URL.Scheme == "https" 38 | } 39 | 40 | // IsUpload returns true if the request has files. 41 | func (c *Context) IsUpload() bool { 42 | ct := c.Request.Header.Get("Content-Type") 43 | return strings.Contains(ct, "multipart/form-data") 44 | } 45 | 46 | // RemoteAddr returns the address of the request. 47 | func (c *Context) RemoteAddr() string { 48 | return strings.TrimSpace(c.Request.RemoteAddr) 49 | } 50 | 51 | // ClientAddr returns the address of the client. 52 | func (c *Context) ClientAddr() string { 53 | if ip := c.ForwardedAddr(); len(ip) > 0 { 54 | return ip 55 | } 56 | return c.RemoteAddr() 57 | } 58 | 59 | // ForwardedAddr returns the address that is in 60 | // X-Real-IP and X-Forwarded-For headers of the request. 61 | func (c *Context) ForwardedAddr() string { 62 | if addrs := c.ForwardedAddrs(); len(addrs) > 0 { 63 | return addrs[0] 64 | } 65 | return "" 66 | } 67 | 68 | // ForwardedAddrs returns the addresses that are in 69 | // X-Real-IP and X-Forwarded-For headers of the request. 70 | func (c *Context) ForwardedAddrs() []string { 71 | rets := make([]string, 0) 72 | names := []string{"X-Real-IP", "X-Forwarded-For"} 73 | for _, name := range names { 74 | if ips := c.Request.Header.Get(name); len(ips) > 0 { 75 | if arr := strings.Split(ips, ","); len(arr) > 0 { 76 | for _, ip := range arr { 77 | if ip = strings.TrimSpace(ip); len(ip) > 0 { 78 | rets = append(rets, ip) 79 | } 80 | } 81 | } 82 | } 83 | } 84 | return rets 85 | } 86 | 87 | // Query returns the URL-encoded query values. 88 | func (c *Context) Query() url.Values { 89 | if c.query == nil { 90 | c.query = c.Request.URL.Query() 91 | } 92 | return c.query 93 | } 94 | 95 | // queryValue returns the first value for the named component 96 | // of the query and the value was found or not. 97 | func (c *Context) queryValue(key string) (string, bool) { 98 | if values := c.Query()[key]; len(values) > 0 { 99 | return values[0], true 100 | } 101 | return "", false 102 | } 103 | 104 | // postFormValue returns the first value for the named component 105 | // of the POST or PUT request body and the value was found or not. 106 | // URL query parameters are ignored. 107 | func (c *Context) postFormValue(key string) (string, bool) { 108 | if c.Request.PostForm == nil { 109 | c.ParseMultipartForm(32 << 20) // 32 MB 110 | } 111 | if values := c.PostForm[key]; len(values) > 0 { 112 | return values[0], true 113 | } 114 | return "", false 115 | } 116 | 117 | // FormFile returns the first file for the provided form key. 118 | func (c *Context) FormFile(key string) (multipart.File, *multipart.FileHeader, error) { 119 | f, h, err := c.Request.FormFile(key) 120 | if err == http.ErrMissingFile { 121 | err = nil 122 | } 123 | return f, h, err 124 | } 125 | 126 | // Has returns true if the key is in the request. 127 | func (c *Context) Has(key string) bool { 128 | _, ok := c.getString(key) 129 | return ok 130 | } 131 | 132 | // String gets the first value associated with the given key. 133 | // If there are no values associated with the key, 134 | // String returns the failover string. 135 | // To access multiple values, use the map directly. 136 | func (c *Context) String(key string, args ...string) string { 137 | if ret, err := c.TryString(key); err == nil { 138 | return ret 139 | } else if len(args) > 0 { 140 | return args[0] 141 | } 142 | return "" 143 | } 144 | 145 | func (c *Context) getString(key string) (string, bool) { 146 | if c.params != nil { 147 | if s := c.params.ByName(key); s != "" { 148 | return s, true 149 | } 150 | } 151 | if v, ok := c.queryValue(key); ok { 152 | return v, true 153 | } else if v, ok := c.postFormValue(key); ok { 154 | return v, true 155 | } 156 | return "", false 157 | } 158 | 159 | // TryString gets the first value associated with the given key. 160 | // If there are no values associated with the key, returns the ErrKeyNotExist. 161 | func (c *Context) TryString(key string) (string, error) { 162 | if s, ok := c.getString(key); ok { 163 | return s, nil 164 | } 165 | return "", ErrKeyNotExist 166 | } 167 | 168 | func (c *Context) Int(key string, args ...int64) int64 { 169 | if ret, err := c.TryInt(key); err == nil { 170 | return ret 171 | } else if len(args) > 0 { 172 | return args[0] 173 | } 174 | return 0 175 | } 176 | 177 | func (c *Context) TryInt(key string) (int64, error) { 178 | s, err := c.TryString(key) 179 | if err != nil { 180 | return 0, err 181 | } 182 | return strconv.ParseInt(s, 10, 64) 183 | } 184 | 185 | func (c *Context) Float(key string, args ...float64) float64 { 186 | if ret, err := c.TryFloat(key); err == nil { 187 | return ret 188 | } else if len(args) > 0 { 189 | return args[0] 190 | } 191 | return 0 192 | } 193 | 194 | func (c *Context) TryFloat(key string) (float64, error) { 195 | s, err := c.TryString(key) 196 | if err != nil { 197 | return 0, err 198 | } 199 | return strconv.ParseFloat(s, 64) 200 | } 201 | 202 | func (c *Context) Bool(key string, args ...bool) bool { 203 | if ret, err := c.TryBool(key); err == nil { 204 | return ret 205 | } else if len(args) > 0 { 206 | return args[0] 207 | } 208 | return false 209 | } 210 | 211 | func (c *Context) TryBool(key string) (bool, error) { 212 | s, err := c.TryString(key) 213 | if err != nil { 214 | return false, err 215 | } 216 | return strconv.ParseBool(s) 217 | } 218 | --------------------------------------------------------------------------------