分享
  1. 首页
  2. 文章

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

string12 · · 2568 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在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
}

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:string12

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

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2568 次点击
1 回复 | 直到 2025年04月30日 14:39:45
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏