一次构建简单HTTP框架的尝试 —— Golang实现

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

程序实现了基本的GET、POST方式路由,不依赖net/http包。

程序只接收content-type为application/json时的POST参数,返回的数据也仅支持json格式。程序仅支持GET、POST方式路由。

router.go:注册路由、启动服务
package http_server

import (
    "fmt"
    "net"
)

type handlerFunc func(*Request)

type methodTree struct {
    method string
    nodes []Node
}

type Node struct {
    path string
    handle handlerFunc
}

type Router struct {
    Trees []methodTree
}

//单条请求数据大小为 40k
const MaxRequestSize = 1024 * 40

func Default() *Router {
    return &Router{}
}

func (r *Router) Run(addr string) error {
    listener, err := net.Listen("tcp4", addr)
    if err != nil {
        return fmt.Errorf("listen error:%v", err)
    }
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println(err)
            continue
        }
        go r.handle(conn)
    }
}

func (r *Router) handle(conn net.Conn) {
    accept := make(chan readerData)
    go r.parseRequest(accept, conn)
    reader := NewReader(conn, MaxRequestSize)
    err := reader.read(accept)
    if err != nil {
        fmt.Println(err)
        //读取数据失败,响应400 bad request错误
        response := newResponse(conn)
        response.errWrite(400)
    }
    conn.Close()
    close(accept)
}

//监听通道,解析请求数据
func (r *Router) parseRequest(accept chan readerData, conn net.Conn) {
    for {
        data, isOk := <- accept
        if !isOk {
            return
        }
        request := newRequest()
        request.response = newResponse(conn)
        request.parse(data)
        r.handleHTTPRequest(request)
    }
}

//调用函数处理http请求
func (r *Router) handleHTTPRequest(request *Request) {
    httpMethod := request.Method
    for _, tree := range r.Trees {
        if tree.method != httpMethod {
            continue
        }
        for _, node := range tree.nodes {
            if node.path == request.Path {
                node.handle(request)
                request.response.write()
                return
            }
        }
    }
    //未找到任何handle 返回404
    request.response.errWrite(404)
}

//设置post路由
func (r *Router) POST(path string, handle handlerFunc) {
    r.addRoute("POST", path, handle)
}

//设置get路由
func (r *Router) GET(path string, handle handlerFunc) {
    r.addRoute("GET", path, handle)
}

//添加路由
func (r *Router) addRoute(method string, path string, handle handlerFunc) {
    var newNodes bool
    for k, v := range r.Trees {
        if method == v.method {
            r.Trees[k].nodes = append(v.nodes, Node{
                path:   path,
                handle: handle,
            })
            newNodes = true
            break
        }
    }
    if !newNodes {
        tree := methodTree{
            method: method,
        }
        tree.nodes = append(tree.nodes, Node{
            path:   path,
            handle: handle,
        })
        r.Trees = append(r.Trees, tree)
    }
}
reader.go:读取并解析HTTP请求行、请求头、请求体
package http_server

import (
    "bytes"
    "fmt"
    "net"
    "strconv"
    "strings"
)

type Reader struct {
    conn net.Conn
    readerData
    buff    []byte
    buffLen int
    start   int
    end     int
}

type readerData struct {
    Line map[string]string    //请求行
    Header map[string]string    //请求头
    Body string    //请求体
}

//实例化
func NewReader(conn net.Conn, buffLen int) *Reader {
    return &Reader{
        conn: conn,
        readerData: readerData{
            Line:   make(map[string]string),
            Header: make(map[string]string),
        },
        buffLen: buffLen,
        buff: make([]byte, buffLen),
    }
}

//读取并解析请求行
func (reader *Reader) parseLine() (isOK bool, err error) {
    index := bytes.Index(reader.buff, []byte{byte('\r'), byte('\n')})
    if index == -1 {
        //没有解析到\r\n返回继续读取
        return
    }
    //读取请求行
    requestLine := string(reader.buff[:index])
    arr := strings.Split(requestLine, " ")
    if len(arr) != 3 {
        return false, fmt.Errorf("bad request line")
    }
    reader.Line["method"] = arr[0]
    reader.Line["url"] = arr[1]
    reader.Line["version"] = arr[2]

    reader.start = index + 2
    return true, nil
}

//读取并解析请求头
func (reader *Reader) parseHeader() {
    if reader.start == reader.end {
        return
    }
    index := bytes.Index(reader.buff[reader.start:], []byte{byte('\r'), byte('\n'), byte('\r'), byte('\n')})
    if index == -1 {
        return
    }
    headerStr := string(reader.buff[reader.start:reader.start+index])
    requestHeader := strings.Split(headerStr, "\r\n")
    for _, v := range requestHeader {
        arr := strings.Split(v, ":")
        if len(arr) < 2 {
            continue
        }
        reader.Header[strings.ToUpper(arr[0])] = strings.ToLower(strings.Trim(strings.Join(arr[1:], ":"), " "))
    }
    reader.start += index + 4
}

//读取并解析请求体
func (reader *Reader) parseBody() (isOk bool, err error) {
    //判断请求头中是否指明了请求体的数据长度
    contentLenStr, ok := reader.Header["CONTENT-LENGTH"]
    if !ok {
        return false, fmt.Errorf("bad request:no content-length")
    }
    contentLen, err := strconv.ParseInt(contentLenStr, 10, 64)
    if err != nil {
        return false, fmt.Errorf("parse content-length error:%s", contentLenStr)
    }
    if contentLen > int64(reader.end - reader.start) {
        //请求体长度不够,返回继续读取
        return false, nil
    }
    reader.Body = string(reader.buff[reader.start:int64(reader.start)+contentLen])
    return true, nil
}

//读取http请求
func (reader *Reader) read(accept chan readerData) (err error) {
    for  {
        if reader.end == reader.buffLen {
            //缓冲区的容量存不了一条请求的数据
            return fmt.Errorf("request is too large:%v", reader)
        }
        buffLen, err := reader.conn.Read(reader.buff)
        if err != nil {
            //连接关闭了
            return nil
        }
        reader.end += buffLen

        //解析请求行
        isOk, err := reader.parseLine()
        if err != nil {
            return fmt.Errorf("parse request line error:%v", err)
        }
        if !isOk {
            continue
        }
        //解析请求头
        reader.parseHeader()
        //如果是post请求,解析请求体
        if len(reader.Header) > 0 && strings.EqualFold(strings.ToUpper(reader.Line["method"]), "POST") {
            isOk, err := reader.parseBody()
            if err != nil {
                return fmt.Errorf("parse request body error:%v", err)
            }
            //读取http请求体未成功
            if !isOk {
                reader.start = 0
                reader.Line = make(map[string]string)
                reader.Header = make(map[string]string)
                continue
            }
        }
        accept <- reader.readerData
        reader.move()
    }
}

//前移上一次未处理完的数据
func (reader *Reader) move() {
    if reader.start == 0 {
        return
    }
    copy(reader.buff, reader.buff[reader.start:reader.end])
    reader.end -= reader.start
    reader.start = 0
}
request.go:解析请求头、请求参数等
package http_server

import (
    "encoding/json"
    "strings"
)

type H map[string]interface{}

type Request struct {
    Path string
    Method string
    headers map[string]string
    queries map[string]string
    posts map[string]string
    *response
}

func newRequest() *Request {
    return &Request{
        headers: make(map[string]string),
        queries: make(map[string]string),
        posts: make(map[string]string),
    }
}

//解析请求内容
func (request *Request) parse(readerData readerData) {
    request.Method = readerData.Line["method"]
    request.headers = readerData.Header

    //解析请求path和get参数
    var queries string
    index := strings.Index(readerData.Line["url"], "?")
    if index == -1 {
        request.Path = readerData.Line["url"]
    }else {
        request.Path = readerData.Line["url"][:index]
        queries = readerData.Line["url"][index+1:]
    }
    if request.Method == "GET" {
        //解析get请求参数
        if queries != "" {
            q := strings.Split(queries, "&")
            for _, v := range q {
                param := strings.Split(v, "=")
                request.queries[param[0]] = param[1]
            }
        }
    }else {
        //判断content-type类型是不是 application/json
        contentTypes, isExist := request.headers["CONTENT-TYPE"]
        if isExist {
            cTypeArr := strings.Split(contentTypes, ";")
            if strings.EqualFold(cTypeArr[0], "application/json") {
                //解析post请求参数
                json.Unmarshal([]byte(readerData.Body), &(request.posts))
            }
        }
    }
}

//获取get请求参数
func (request *Request) Query(name string) string {
    val, isExist := request.queries[name]
    if isExist {
        return val
    }
    return ""
}

//获取post请求参数
func (request *Request) Post(name string) string {
    val, isExist := request.posts[name]
    if isExist {
        return val
    }
    return ""
}

//获取get请求参数
func (request *Request) DefaultQuery(name, def string) string {
    val, isExist := request.queries[name]
    if isExist {
        return val
    }
    return def
}

//获取post请求参数
func (request *Request) DefaultPost(name, def string) string {
    val, isExist := request.posts[name]
    if isExist {
        return val
    }
    return def
}

//获取请求头
func (request *Request) GetHeader(name string) string {
    val, isExist := request.posts[strings.ToUpper(name)]
    if isExist {
        return val
    }
    return ""
}

//设置要返回的json数据
func (request *Request) Json(code int, obj interface{}) {
    ret, err := json.Marshal(obj)
    if err == nil {
        //设置content-length
        request.response.bodyLen = len(ret)
        request.response.body = ret
    }
    request.response.status = code
}

//设置响应头
func (request *Request) Header(name string, val string) {
    if _, isExist := request.response.headers[name]; !isExist {
        request.response.headers[strings.ToLower(name)] = val
    }
}
response.go:构造HTTP响应
package http_server

import (
    "fmt"
    "net"
    "strconv"
)

type response struct {
    status int
    body []byte
    bodyLen int
    headers map[string]string
    buff []byte
    conn net.Conn
}

func newResponse (conn net.Conn) *response {
    return &response{
        conn:    conn,
        headers: make(map[string]string),
    }
}

//响应行
func (response *response) writeLine() {
    line := fmt.Sprintf("HTTP/1.1 %d OK\r\n", response.status)
    response.buff = append(response.buff, []byte(line)...)
}

//响应头
func (response *response) writeHeader() {
    response.headers["server"] = "^_^"
    response.headers["content-type"] = "application/json"
    response.headers["content-length"] = strconv.FormatInt(int64(response.bodyLen), 10)
    for k, v := range response.headers {
        response.buff = append(response.buff, []byte(fmt.Sprintf("%s: %v\r\n", k, v))...)
    }
    response.buff = append(response.buff, []byte("\r\n")...)
}

func (response *response) write() {
    response.writeLine()
    response.writeHeader()
    response.buff = append(response.buff, response.body...)
    response.conn.Write(response.buff)
}

func (response *response) errWrite(status int) {
    response.status = status
    response.body = []byte("Request Error")
    response.bodyLen = len(response.body)
    response.write()
}

项目放在Github上,欢迎给star~


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

本文来自:Segmentfault

感谢作者:match

查看原文:一次构建简单HTTP框架的尝试 —— Golang实现

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

820 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传