Golang Implementing a lightweight web framework

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

Often see a lot of students in the plan to use go to develop the time will ask what HTTP framework is better. In fact, the HTTP package of Go is very powerful, for the general HTTP Rest API development, can not use the framework to achieve the desired function.

We started experimenting with less than 100 lines of code to customize the basic functional framework.

First, consider the basic requirements:

    1. Output access days, you need to know:
      • Method
      • Status code
      • wr.
      • Response consumption time
      • Response Content-length
    2. Error trapping, catching an error when an HTTP request is unexpected, returning exception information

The above is a few basic needs, the future may have a lot of, so should try to design into a form of middleware to deal with the future changes in demand, so that the function according to demand increases or decreases.

We can put the HTTP framework middleware design than onion, one layer at a level, to the middle of the business layer, and then a layer of out.

Draw the process out like this:

Http:| LogRequst|    ErrCatch|        Cookie|          Handler|        cookie|    ErrCatchV LogRequst

The invocation process is similar to each middleware layer by package, which conforms to the process of the function stack layer call.

Note: Because this small frame is eventually going to be http. Serve or http. Listenandserve is called, so HTTP needs to be implemented . Handler interface, the received parameter is http. Responsewriter and *http. Request

All right! The target is determined, and we're starting to make it work.

First you need to define a struct structure, where you need to save the middleware, and the final HTTP to execute . Handler

// MiddlewareFunc filter typetype MiddlewareFunc func(ResponseWriteReader, *http.Request, func())// MiddlewareServe server structtype MiddlewareServe struct {    middlewares []MiddlewareFunc    Handler     http.Handler}

Here's a problem because the default is to receive the parameter http. The Responsewriter interface is a write-only interface that cannot be read, but we need to be able to read status code and content-length . This time the magic of interface design is reflected, redefine an interface and contain http. Responsewriter , adding the ability to read status code and content-length

// ResponseWriteReader for middlewaretype ResponseWriteReader interface {    StatusCode() int    ContentLength() int    http.ResponseWriter}

Define a warp struct implementation Responsewritereader interface

// WrapResponseWriter implement ResponseWriteReader interfacetype WrapResponseWriter struct {    status int    length int    http.ResponseWriter}// NewWrapResponseWriter create wrapResponseWriterfunc NewWrapResponseWriter(w http.ResponseWriter) *WrapResponseWriter {    wr := new(WrapResponseWriter)    wr.ResponseWriter = w    wr.status = 200    return wr}// WriteHeader write status codefunc (p *WrapResponseWriter) WriteHeader(status int) {    p.status = status    p.ResponseWriter.WriteHeader(status)}func (p *WrapResponseWriter) Write(b []byte) (int, error) {    n, err := p.ResponseWriter.Write(b)    p.length += n    return n, err}// StatusCode return status codefunc (p *WrapResponseWriter) StatusCode() int {    return p.status}// ContentLength return content lengthfunc (p *WrapResponseWriter) ContentLength() int {    return p.length}

Next,Middlewareserve itself needs to conform to http. Handler, so we need to define servehttp.

// ServeHTTP for http.Handler interfacefunc (p *MiddlewareServe) ServeHTTP(w http.ResponseWriter, r *http.Request) {    i := 0  // warp http.ResponseWriter 可以让中间件读取到 status code    wr := NewWrapResponseWriter(w)    var next func() // next 函数指针    next = func() {        if i < len(p.middlewares) {            i++            p.middlewares[i-1](wr, r, next)        } else if p.Handler != nil {            p.Handler.ServeHTTP(wr, r)        }    }    next()}

Then add a method to insert the middleware

// Use push MiddlewareFuncfunc (p *MiddlewareServe) Use(funcs ...MiddlewareFunc) { // 可以一次插入一个或多个    for _, f := range funcs {        p.middlewares = append(p.middlewares, f)    }}

Here, a small framework that supports middleware is defined, with comments less than 80 lines of code

Let's start by implementing a few middleware tests.

// LogRequest print a request statusfunc LogRequest(w ResponseWriteReader, r *http.Request, next func()) {    t := time.Now()    next()    log.Printf("%v %v %v use time %v content-length %v",        r.Method,        w.StatusCode(),        r.URL.String(),        time.Now().Sub(t).String(),        w.ContentLength())}

This function prints out the HTTP request Method, status code, URL, processing request consumption time, response content-length

Test it.

package mainimport (    "fmt"    "net/http")func helloHandle(w http.ResponseWriter, r *http.Request) {    fmt.Fprintf(w, " hello ! this's a http request \n method %v \n request url is %v ", r.Method, r.URL.String())}func main() {    // create middleware server    s := new(MiddlewareServe)    s.Handler = http.HandlerFunc(helloHandle)    s.Use(LogRequest)    // start server    fmt.Println(http.ListenAndServe(":3000", s))}

Run

$ go run *.go$ curl 127.0.0.1:3000> hello ! this's a http request> method GET> request url is# 输出日志> 2016/04/24 02:28:12 GET 200 / use time 61.717µs content-length 64$ curl 127.0.0.1:3000/hello/go> hello ! this's a http request> method GET> request url is /hello/go# 输出日志> 2016/04/24 02:31:36 GET 200 /hello/go use time 28.207µs content-length 72

Or use the browser to request an address to view the effect

    • http://127.0.0.1:3000
    • Http://127.0.0.1:3000/hello/go

Add one more error capture middleware:

// ErrCatch catch and recoverfunc ErrCatch(w ResponseWriteReader, r *http.Request, next func()) {    defer func() {        if err := recover(); err != nil {            fmt.Println(err)            debug.PrintStack()            w.WriteHeader(http.StatusInternalServerError) // 500        }    }()    next()}

Test

package mainimport (    "fmt"    "net/http")func helloHandle(w http.ResponseWriter, r *http.Request) {    fmt.Fprintf(w, " hello ! this's a http request \n method %v \n request url is %v \n", r.Method, r.URL.String())}func panicHandle(w http.ResponseWriter, r *http.Request) {    panic("help me !")}func main() {    // create middleware server    s := new(MiddlewareServe)    route := http.NewServeMux()    route.Handle("/hello", http.HandlerFunc(helloHandle))    route.Handle("/panic", http.HandlerFunc(panicHandle))    s.Handler = route    s.Use(LogRequest, ErrCatch)    // start server    fmt.Println(http.ListenAndServe(":3000", s))}

Run

$ curl -i 127.0.0.1:3000/panic> HTTP/1.1 500 Internal Server Error> Date: Sat, 23 Apr 2016 18:51:12 GMT> Content-Length: 0> Content-Type: text/plain; charset=utf-8# log> help me !> ... # debug.Stack> 2016/04/24 02:51:12 GET 500 /panic use time 142.885µs content-length 0$ curl -i 127.0.0.1:3000/hello/go> HTTP/1.1 404 Not Found> Content-Type: text/plain; charset=utf-8> X-Content-Type-Options: nosniff> Date: Sat, 23 Apr 2016 18:55:30 GMT> Content-Length: 19>> 404 page not found# log2016/04/24 02:55:30 GET 404 /hello/go use time 41.14µs content-length 19

Here, a flexible core is realized. I try to use only standard packages, not the introduction of third-party packages, I hope this may help the students just learn to learn more about the Net.http package, of course, in real-world use can be introduced to other HTTP-compliant . Router substitution for Handler interface Servemux

Some students may have seen it, since Middlewareserve implemented http. Handler that you can hang into the router . Yes, that would be equivalent to customizing the Middlewareserve for a certain path, such as some interfaces that require permission validation, and I'll try write permission validation in the next article.

Calls involving templates I'm not going to write because this is really a scripting language better suited

All code for GitHub address: https://github.com/ifanfan/golearn/tree/master/websrv

The first blog I wrote I hope to continue to write

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.