golang中使用原生的http包编写一个web服务

栏目: Go · 发布时间: 5年前

内容简介:在golang中实现一个简单的web服务很简单,代码如下:首先我们是通过ListenAndServe来监听本地端口的,之后ListenAndServe将收到的新建一个Response连同收到的Request作为参数调用ServeMux结构体的ServeHTTP(省略了中间过程).ServeHTTP将Request作为参数调用

在golang中实现一个简单的web服务很简单,代码如下:

package main
import (
    "net/http"
    "fmt"
)
func main()  {
    http.HandleFunc("/", hello)

    http.ListenAndServe(":9090", nil)
}
func hello(w http.ResponseWriter, r *http.Request)  {
    fmt.Fprintf(w, "hello world")
}

首先我们是通过ListenAndServe来监听本地端口的,之后ListenAndServe将收到的新建一个Response连同收到的Request

作为参数调用ServeMux结构体的ServeHTTP(省略了中间过程).ServeHTTP将Request作为参数调用

Handler函数,Handler的返回值为一个Handler类型的接口,ServeHTTP会调用接口实现的ServeHTTP处理Response.

如果Request.URL.Path中有不合法的内容,则调用cleanPath清理,随后将Request.Host以及清理后的

内容传入handler函数,随后返回一个RedirectHandler以及handler所返回的路径。如果Request.URL.Path合法,那么

直接调用handler,返回值与handler返回值相同。

handler中通过判断ServeMux.hosts来决定是否实现pattern = r.Host + r.URL.Path.之后将pattern作为参数调用match,并将

match的返回值返回.

match的判别方式比较”有趣”,它虽然没实现为树形结构(只是用了映射),但是搜索的方法就是树形,因为URL路径就是个树形.它按照树的根节点

与子节点的关系进行判断,譬如路径”/home/select/usercourse”,match在匹配的时候会首先匹配到”/”(假如我们注册了),其次是”/home”,

之后逐层匹配下来,假如我们没注册过”/home/select/usercourse”,但是注册了”/home/select/”,那么match就会匹配到这一层.然后返回

“/home/select/”的Handler以及url(pattern).match函数的匹配规则实现在pathMatch

ServerMux结构体

type ServeMux struct {
        mu    sync.RWMutex
        m     map[string]muxEntry
        hosts bool // whether any patterns contain hostnames
    }

type muxEntry struct {
        explicit bool
        h        Handler
        pattern  string
    }

NewServeMux()

DefaultServeMux变量就是直接调用这个函数,那么这个函数只make了一个变量m,也就是为map分配了空间,在golang的语法中,结构体里未申明的变量也是存在的(即分配了内存)

pathMath()

这个函数是ServeMux用来匹配路劲的主要函数,所以看一下策略还是很重要的,函数中pattern是我们注册的路劲,path是用户请求的路径

func pathMatch(pattern, path string) bool {
    if len(pattern) == 0 {
        // should not happen
        return false
    }
    n := len(pattern)
    if pattern[n-1] != '/' {
        return pattern == path
    }
    return len(path) >= n && path[0:n] == pattern
}

如果我们挂在的路径不是以 / 结尾的,那么就直接判断两个参数是否相同,如果是以 / 结尾的,只要path的路径包含pattern那么久被判定是匹配,也就是说,如若我注册了 /home/select/ ,那么 /home/select/hello 也会被定位到 /home/select/ 上面,挂在的HanderFunc上面,这样做相当于为路径设置了一个index,不符合规则的URL都会被Redirct到这个index上。

* ServeMux.Handler()

注释写的很清楚,这个函数就是处理URL,然后调用*ServeMux.handler().首先调用cleanPath清理请求URL中的不合法内容。如果存在不合法内容,

则将清理过的URL交由*ServeMux.handler()处理并获得匹配到的pattern,然后修改url.Path的内容并调用RedirectHandler.

如果内容合法,则直接调用*ServeMux.handler()并返回结果

  • ServeMux.handler()

    调用ServeMux.match()(封装了pathMatch函数)来获得匹配到的Handler以及对应pattern,如果ServeMux.hosts==true,那么

    传入的参数为host + path,如果找不到的话,调用NotFoundHandler函数,并将其结果返回.

  • ServeMux.Handle()

    Handle函数是用来注册路径与处理过程的.如果该路径已经存在了一个用户注册的Handler则会panic(意思就是说不支持覆盖).判别了合法参数以后就将

    pattern作为key,新建一个muxEntry类型变量作为value加入到map中。

if pattern[0] != ‘/’ {

mux.hosts = true

}

这是这个函数中比较有意思的一个部分,通过这里我们可以看到如果注册路径的时候并不是以’/’开头的,那么ServeMux就会开启hosts,然后会在

请求到达的时候将URL.Host和URL.Path连接在一起放入match中寻找,具体信息请看这里

接下来是关于路径的处理,也就是关于”/home”与”/home/”的区别.我们先来看看作者怎么说

// Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.

如果路径的末尾是以’/’结尾并且该路径去掉末尾的’/’以后并没有被注册.那么将会去掉’/’并且为其绑定一个Redirect到现在的路径.

我自己写起来都觉得绕,举个例子就清楚了.

我注册了一个路径”/home/”,但是没有注册”/home”,那么如果用户访问了”/home”会发生什么呢?是的,会被Redirect到”/home/”.

需要注意的是,这里的muxEntry中的explicit没有填,也就是说是false,那么即是可以覆盖的.

  • ServeMux.ServeHTTP()

    ServeHTTP会检测非法的URI(* )

    如果通过检测就会调用自身的Handler()来返回注册的Handler,随后调用Handler的ServeHTTP方法

在http.ListenAndServe中的第二个参数传递处理hander可以自定义,这样就可以自己实现一个路由,在处理函数中必须实现ServeHTTP函数,这个函数在handler中直接执行。例子如下:

package routes

import (
    "encoding/json"
    "encoding/xml"
    "io/ioutil"
    "net/http"
    "net/url"
    "path/filepath"
    "regexp"
    "strconv"
    "strings"
)

const (
    CONNECT = "CONNECT"
    DELETE  = "DELETE"
    GET     = "GET"
    HEAD    = "HEAD"
    OPTIONS = "OPTIONS"
    PATCH   = "PATCH"
    POST    = "POST"
    PUT     = "PUT"
    TRACE   = "TRACE"
)

//commonly used mime-types
const (
    applicationJson = "application/json"
    applicationXml  = "application/xml"
    textXml         = "text/xml"
)

type route struct {
    method  string
    regex   *regexp.Regexp
    params  map[int]string
    handler http.HandlerFunc
}

type RouteMux struct {
    routes  []*route
    filters []http.HandlerFunc
}

func New() *RouteMux {
    return &RouteMux{}
}

// Get adds a new Route for GET requests.
func (m *RouteMux) Get(pattern string, handler http.HandlerFunc) {
    m.AddRoute(GET, pattern, handler)
}

// Put adds a new Route for PUT requests.
func (m *RouteMux) Put(pattern string, handler http.HandlerFunc) {
    m.AddRoute(PUT, pattern, handler)
}

// Del adds a new Route for DELETE requests.
func (m *RouteMux) Del(pattern string, handler http.HandlerFunc) {
    m.AddRoute(DELETE, pattern, handler)
}

// Patch adds a new Route for PATCH requests.
func (m *RouteMux) Patch(pattern string, handler http.HandlerFunc) {
    m.AddRoute(PATCH, pattern, handler)
}

// Post adds a new Route for POST requests.
func (m *RouteMux) Post(pattern string, handler http.HandlerFunc) {
    m.AddRoute(POST, pattern, handler)
}

// Adds a new Route for Static http requests. Serves
// static files from the specified directory
func (m *RouteMux) Static(pattern string, dir string) {
    //append a regex to the param to match everything
    // that comes after the prefix
    pattern = pattern + "(.+)"
    m.AddRoute(GET, pattern, func(w http.ResponseWriter, r *http.Request) {
        path := filepath.Clean(r.URL.Path)
        path = filepath.Join(dir, path)
        http.ServeFile(w, r, path)
    })
}

// Adds a new Route to the Handler
func (m *RouteMux) AddRoute(method string, pattern string, handler http.HandlerFunc) {

    //split the url into sections
    parts := strings.Split(pattern, "/")

    //find params that start with ":"
    //replace with regular expressions
    j := 0
    params := make(map[int]string)
    for i, part := range parts {
        if strings.HasPrefix(part, ":") {
            expr := "([^/]+)"
            //a user may choose to override the defult expression
            // similar to expressjs: ‘/user/:id([0-9]+)’
            if index := strings.Index(part, "("); index != -1 {
                expr = part[index:]
                part = part[:index]
            }
            params[j] = part
            parts[i] = expr
            j++
        }
    }

    //recreate the url pattern, with parameters replaced
    //by regular expressions. then compile the regex
    pattern = strings.Join(parts, "/")
    regex, regexErr := regexp.Compile(pattern)
    if regexErr != nil {
        //TODO add error handling here to avoid panic
        panic(regexErr)
        return
    }

    //now create the Route
    route := &route{}
    route.method = method
    route.regex = regex
    route.handler = handler
    route.params = params

    //and finally append to the list of Routes
    m.routes = append(m.routes, route)
}

// Filter adds the middleware filter.
func (m *RouteMux) Filter(filter http.HandlerFunc) {
    m.filters = append(m.filters, filter)
}

// FilterParam adds the middleware filter iff the REST URL parameter exists.
func (m *RouteMux) FilterParam(param string, filter http.HandlerFunc) {
    if !strings.HasPrefix(param,":") {
        param = ":"+param
    }

    m.Filter(func(w http.ResponseWriter, r *http.Request) {
        p := r.URL.Query().Get(param)
        if len(p) > 0 { filter(w, r) }
    })
}

// Required by http.Handler interface. This method is invoked by the
// http server and will handle all page routing
func (m *RouteMux) ServeHTTP(rw http.ResponseWriter, r *http.Request) {

    requestPath := r.URL.Path

    //wrap the response writer, in our custom interface
    w := &responseWriter{writer: rw}

    //find a matching Route
    for _, route := range m.routes {

        //if the methods don't match, skip this handler
        //i.e if request.Method is 'PUT' Route.Method must be 'PUT'
        if r.Method != route.method {
            continue
        }

        //check if Route pattern matches url
        if !route.regex.MatchString(requestPath) {
            continue
        }

        //get submatches (params)
        matches := route.regex.FindStringSubmatch(requestPath)

        //double check that the Route matches the URL pattern.
        if len(matches[0]) != len(requestPath) {
            continue
        }

        if len(route.params) > 0 {
            //add url parameters to the query param map
            values := r.URL.Query()
            for i, match := range matches[1:] {
                values.Add(route.params[i], match)
            }

            //reassemble query params and add to RawQuery
            r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery
            //r.URL.RawQuery = url.Values(values).Encode()
        }

        //execute middleware filters
        for _, filter := range m.filters {
            filter(w, r)
            if w.started {
                return
            }
        }

        //Invoke the request handler
        route.handler(w, r)
        break
    }

    //if no matches to url, throw a not found exception
    if w.started == false {
        http.NotFound(w, r)
    }
}

// -----------------------------------------------------------------------------
// Simple wrapper around a ResponseWriter

// responseWriter is a wrapper for the http.ResponseWriter
// to track if response was written to. It also allows us
// to automatically set certain headers, such as Content-Type,
// Access-Control-Allow-Origin, etc.
type responseWriter struct {
    writer  http.ResponseWriter
    started bool
    status  int
}

// Header returns the header map that will be sent by WriteHeader.
func (w *responseWriter) Header() http.Header {
    return w.writer.Header()
}

// Write writes the data to the connection as part of an HTTP reply,
// and sets `started` to true
func (w *responseWriter) Write(p []byte) (int, error) {
    w.started = true
    return w.writer.Write(p)
}

// WriteHeader sends an HTTP response header with status code,
// and sets `started` to true
func (w *responseWriter) WriteHeader(code int) {
    w.status = code
    w.started = true
    w.writer.WriteHeader(code)
}

// -----------------------------------------------------------------------------
// Below are helper functions to replace boilerplate
// code that serializes resources and writes to the
// http response.

// ServeJson replies to the request with a JSON
// representation of resource v.
func ServeJson(w http.ResponseWriter, v interface{}) {
    content, err := json.MarshalIndent(v, "", "  ")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Length", strconv.Itoa(len(content)))
    w.Header().Set("Content-Type", applicationJson)
    w.Write(content)
}

// ReadJson will parses the JSON-encoded data in the http
// Request object and stores the result in the value
// pointed to by v.
func ReadJson(r *http.Request, v interface{}) error {
    body, err := ioutil.ReadAll(r.Body)
    r.Body.Close()
    if err != nil {
        return err
    }
    return json.Unmarshal(body, v)
}

// ServeXml replies to the request with an XML
// representation of resource v.
func ServeXml(w http.ResponseWriter, v interface{}) {
    content, err := xml.Marshal(v)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Length", strconv.Itoa(len(content)))
    w.Header().Set("Content-Type", "text/xml; charset=utf-8")
    w.Write(content)
}

// ReadXml will parses the XML-encoded data in the http
// Request object and stores the result in the value
// pointed to by v.
func ReadXml(r *http.Request, v interface{}) error {
    body, err := ioutil.ReadAll(r.Body)
    r.Body.Close()
    if err != nil {
        return err
    }
    return xml.Unmarshal(body, v)
}

// ServeFormatted replies to the request with
// a formatted representation of resource v, in the
// format requested by the client specified in the
// Accept header.
func ServeFormatted(w http.ResponseWriter, r *http.Request, v interface{}) {
    accept := r.Header.Get("Accept")
    switch accept {
    case applicationJson:
        ServeJson(w, v)
    case applicationXml, textXml:
        ServeXml(w, v)
    default:
        ServeJson(w, v)
    }

    return
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Game Programming Patterns

Game Programming Patterns

Robert Nystrom / Genever Benning / 2014-11-2 / USD 39.95

The biggest challenge facing many game programmers is completing their game. Most game projects fizzle out, overwhelmed by the complexity of their own code. Game Programming Patterns tackles that exac......一起来看看 《Game Programming Patterns》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具