From d2be74a4f23c5beb239d1db0c1707ae29ec13dc8 Mon Sep 17 00:00:00 2001 From: astaxie Date: Wed, 21 Aug 2013 13:24:14 +0800 Subject: [PATCH] add beehttp module --- beehttp/context.go | 29 ++++++ beehttp/input.go | 155 +++++++++++++++++++++++++++++++ beehttp/output.go | 221 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 405 insertions(+) create mode 100644 beehttp/context.go create mode 100644 beehttp/input.go create mode 100644 beehttp/output.go diff --git a/beehttp/context.go b/beehttp/context.go new file mode 100644 index 00000000..bae7506c --- /dev/null +++ b/beehttp/context.go @@ -0,0 +1,29 @@ +package beehttp + +import ( + "net/http" +) + +type Context struct { + Input *BeegoInput + Output *BeegoOutput + Request *http.Request + ResponseWriter http.ResponseWriter +} + +func (ctx *Context) Redirect(status int, localurl string) { + ctx.Output.Header("Location", localurl) + ctx.Output.SetStatus(status) +} + +func (ctx *Context) WriteString(content string) { + ctx.Output.Body([]byte(content)) +} + +func (ctx *Context) GetCookie(key string) string { + return ctx.Input.Cookie(key) +} + +func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { + ctx.Output.Cookie(name, value, others...) +} diff --git a/beehttp/input.go b/beehttp/input.go new file mode 100644 index 00000000..f6dd2d3f --- /dev/null +++ b/beehttp/input.go @@ -0,0 +1,155 @@ +package beehttp + +import ( + "bytes" + "github.com/astaxie/beego/session" + "io/ioutil" + "net/http" + "strconv" + "strings" +) + +type BeegoInput struct { + CruSession session.SessionStore + Param map[string]string + req *http.Request + RequestBody []byte +} + +func NewInput(req *http.Request) *BeegoInput { + return &BeegoInput{ + Param: make(map[string]string), + req: req, + } +} + +func (input *BeegoInput) Protocol() string { + return input.req.Proto +} + +func (input *BeegoInput) Uri() string { + return input.req.RequestURI +} + +func (input *BeegoInput) Url() string { + return input.req.URL.String() +} + +func (input *BeegoInput) Site() string { + return input.Scheme() + "://" + input.Domain() +} + +func (input *BeegoInput) Scheme() string { + return input.req.URL.Scheme +} + +func (input *BeegoInput) Domain() string { + return input.Host() +} + +func (input *BeegoInput) Host() string { + if input.req.Host != "" { + hostParts := strings.Split(input.req.Host, ":") + if len(hostParts) > 0 { + return hostParts[0] + } + return input.req.Host + } + return "localhost" +} + +func (input *BeegoInput) Method() string { + return input.req.Method +} + +func (input *BeegoInput) Is(method string) bool { + return input.Method() == method +} + +func (input *BeegoInput) IsAjax() bool { + return input.Header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest" +} + +func (input *BeegoInput) IsSecure() bool { + return input.Scheme() == "https" +} + +func (input *BeegoInput) IsUpload() bool { + return input.req.MultipartForm != nil +} + +func (input *BeegoInput) IP() string { + ips := input.Proxy() + if len(ips) > 0 && ips[0] != "" { + return ips[0] + } + ip := strings.Split(input.req.RemoteAddr, ":") + if len(ip) > 0 { + return ip[0] + } + return "127.0.0.1" +} + +func (input *BeegoInput) Proxy() []string { + if ips := input.Header("HTTP_X_FORWARDED_FOR"); ips != "" { + return strings.Split(ips, ",") + } + return []string{} +} + +func (input *BeegoInput) Refer() string { + return input.Header("HTTP_REFERER") +} + +func (input *BeegoInput) SubDomains() string { + parts := strings.Split(input.Host(), ".") + return strings.Join(parts[len(parts)-2:], ".") +} + +func (input *BeegoInput) Port() int { + parts := strings.Split(input.req.Host, ":") + if len(parts) == 2 { + port, _ := strconv.Atoi(parts[1]) + return port + } + return 80 +} + +func (input *BeegoInput) UserAgent() string { + return input.Header("HTTP_USER_AGENT") +} + +func (input *BeegoInput) Params(key string) string { + if v, ok := input.Param[key]; ok { + return v + } + return "" +} + +func (input *BeegoInput) Query(key string) string { + return input.req.Form.Get(key) +} + +func (input *BeegoInput) Header(key string) string { + return input.req.Header.Get(key) +} + +func (input *BeegoInput) Cookie(key string) string { + ck, err := input.req.Cookie(key) + if err != nil { + return "" + } + return ck.Value +} + +func (input *BeegoInput) Session(key interface{}) interface{} { + return input.CruSession.Get(key) +} + +func (input *BeegoInput) Body() []byte { + requestbody, _ := ioutil.ReadAll(input.req.Body) + input.req.Body.Close() + bf := bytes.NewBuffer(requestbody) + input.req.Body = ioutil.NopCloser(bf) + return requestbody +} diff --git a/beehttp/output.go b/beehttp/output.go new file mode 100644 index 00000000..10be04d4 --- /dev/null +++ b/beehttp/output.go @@ -0,0 +1,221 @@ +package beehttp + +import ( + "bytes" + "compress/flate" + "compress/gzip" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "mime" + "net/http" + "path/filepath" + "strconv" + "strings" +) + +type BeegoOutput struct { + context *Context + Status int + EnableGzip bool + res http.ResponseWriter +} + +func NewOutput(res http.ResponseWriter) *BeegoOutput { + return &BeegoOutput{ + res: res, + } +} + +func (output *BeegoOutput) Header(key, val string) { + output.res.Header().Set(key, val) +} + +func (output *BeegoOutput) Body(content []byte) { + output_writer := output.res.(io.Writer) + if output.EnableGzip == true && output.context.Input.Header("Accept-Encoding") != "" { + splitted := strings.SplitN(output.context.Input.Header("Accept-Encoding"), ",", -1) + encodings := make([]string, len(splitted)) + + for i, val := range splitted { + encodings[i] = strings.TrimSpace(val) + } + for _, val := range encodings { + if val == "gzip" { + output.Header("Content-Encoding", "gzip") + output_writer, _ = gzip.NewWriterLevel(output.res, gzip.BestSpeed) + + break + } else if val == "deflate" { + output.Header("Content-Encoding", "deflate") + output_writer, _ = flate.NewWriter(output.res, flate.BestSpeed) + break + } + } + } else { + output.Header("Content-Length", strconv.Itoa(len(content))) + } + output_writer.Write(content) + switch output_writer.(type) { + case *gzip.Writer: + output_writer.(*gzip.Writer).Close() + case *flate.Writer: + output_writer.(*flate.Writer).Close() + case io.WriteCloser: + output_writer.(io.WriteCloser).Close() + } +} + +func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) { + var b bytes.Buffer + fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value)) + if len(others) > 0 { + switch others[0].(type) { + case int: + if others[0].(int) > 0 { + fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int)) + } else if others[0].(int) < 0 { + fmt.Fprintf(&b, "; Max-Age=0") + } + case int64: + if others[0].(int64) > 0 { + fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int64)) + } else if others[0].(int64) < 0 { + fmt.Fprintf(&b, "; Max-Age=0") + } + case int32: + if others[0].(int32) > 0 { + fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int32)) + } else if others[0].(int32) < 0 { + fmt.Fprintf(&b, "; Max-Age=0") + } + } + } + if len(others) > 1 { + fmt.Fprintf(&b, "; Path=%s", sanitizeValue(others[1].(string))) + } + if len(others) > 2 { + fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(others[2].(string))) + } + if len(others) > 3 { + fmt.Fprintf(&b, "; Secure") + } + if len(others) > 4 { + fmt.Fprintf(&b, "; HttpOnly") + } + output.res.Header().Add("Set-Cookie", b.String()) +} + +var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") + +func sanitizeName(n string) string { + return cookieNameSanitizer.Replace(n) +} + +var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ") + +func sanitizeValue(v string) string { + return cookieValueSanitizer.Replace(v) +} + +func (output *BeegoOutput) Json(data string) error { + output.Header("Content-Type", "application/json;charset=UTF-8") + content, err := json.Marshal(data) + if err != nil { + return err + } + output.Body(content) + return nil +} + +func (output *BeegoOutput) Jsonp(data string) error { + output.Header("Content-Type", "application/javascript;charset=UTF-8") + content, err := json.Marshal(data) + if err != nil { + return err + } + callback := output.context.Input.Query("callback") + if callback == "" { + return errors.New(`"callback" parameter required`) + } + callback_content := bytes.NewBufferString(callback) + callback_content.WriteString("(") + callback_content.Write(content) + callback_content.WriteString(");\r\n") + output.Body(callback_content.Bytes()) + return nil +} + +func (output *BeegoOutput) Xml(data string) error { + output.Header("Content-Type", "application/xml;charset=UTF-8") + content, err := xml.Marshal(data) + if err != nil { + return err + } + output.Body(content) + return nil +} + +func (output *BeegoOutput) Download(file string) { + output.Header("Content-Description", "File Transfer") + output.Header("Content-Type", "application/octet-stream") + output.Header("Content-Disposition", "attachment; filename="+filepath.Base(file)) + output.Header("Content-Transfer-Encoding", "binary") + output.Header("Expires", "0") + output.Header("Cache-Control", "must-revalidate") + output.Header("Pragma", "public") + http.ServeFile(output.res, output.context.Request, file) +} + +func (output *BeegoOutput) ContentType(ext string) { + if !strings.HasPrefix(ext, ".") { + ext = "." + ext + } + ctype := mime.TypeByExtension(ext) + if ctype != "" { + output.Header("Content-Type", ctype) + } +} + +func (output *BeegoOutput) SetStatus(status int) { + output.res.WriteHeader(status) + output.Status = status +} + +func (output *BeegoOutput) IsCachable(status int) bool { + return output.Status >= 200 && output.Status < 300 || output.Status == 304 +} + +func (output *BeegoOutput) IsEmpty(status int) bool { + return output.Status == 201 || output.Status == 204 || output.Status == 304 +} + +func (output *BeegoOutput) IsOk(status int) bool { + return output.Status == 200 +} + +func (output *BeegoOutput) IsSuccessful(status int) bool { + return output.Status >= 200 && output.Status < 300 +} + +func (output *BeegoOutput) IsRedirect(status int) bool { + return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307 +} + +func (output *BeegoOutput) IsForbidden(status int) bool { + return output.Status == 403 +} + +func (output *BeegoOutput) IsNotFound(status int) bool { + return output.Status == 404 +} + +func (output *BeegoOutput) IsClientError(status int) bool { + return output.Status >= 400 && output.Status < 500 +} + +func (output *BeegoOutput) IsServerError(status int) bool { + return output.Status >= 500 && output.Status < 600 +}