這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
服務端收到client發送的Http資料後怎麼解析成具體的Request對象呢?下面來看看golang是如何處理的
首先看一個具體應用執行個體:僅僅包含HTTP裡面的Method,URL和Proto
package mainimport ("bufio""fmt"_ "io""net/http""net/textproto""strings")func main() {request()}//當然這個執行個體比較簡單,僅僅是通過\n來解析出幾個欄位而已func request() {paths := "/xxx"br := bufio.NewReader(strings.NewReader("GET " + paths + " HTTP/1.1\r\nHost: test\r\n\r\n"))tp := textproto.NewReader(br)var s stringvar err errorif s, err = tp.ReadLine(); err != nil {fmt.Printf("prase err: %v \n", err)}fmt.Printf("readLine restult: %v \n", s)req := new(http.Request)req.Method, req.RequestURI, req.Proto, _ = parseRequestLine(s)fmt.Printf("prase result %v \n", req)}func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {s1 := strings.Index(line, " ")s2 := strings.Index(line[s1+1:], " ")if s1 < 0 || s2 < 0 {return}s2 += s1 + 1return line[:s1], line[s1+1 : s2], line[s2+1:], true}//--------------------------------輸出結果為://readLine restult: GET /xxx HTTP/1.1 //prase result &{GET <nil> HTTP/1.1 0 0 map[] <nil> 0 [] false map[] map[] <nil> map[] /xxx <nil>}
再來深入看看golang的http源碼
func ReadRequest(b *bufio.Reader) (req *Request, err error) {tp := newTextprotoReader(b) //封裝一個Reader結構req = new(Request)// First line: GET /index.html HTTP/1.0 //最開始的一行資訊的解析var s stringif s, err = tp.ReadLine(); err != nil {return nil, err}defer func() {putTextprotoReader(tp)if err == io.EOF {err = io.ErrUnexpectedEOF}}()var ok bool //最開始的一行資訊的解析req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)if !ok {return nil, &badStringError{"malformed HTTP request", s}}rawurl := req.RequestURI //client請求的URL//HTTP對應的ProtoMajor和ProtoMinorif req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok {return nil, &badStringError{"malformed HTTP version", req.Proto}}// CONNECT requests are used two different ways, and neither uses a full URL:// The standard use is to tunnel HTTPS through an HTTP proxy.// It looks like "CONNECT www.google.com:443 HTTP/1.1", and the parameter is// just the authority section of a URL. This information should go in req.URL.Host.//// The net/rpc package also uses CONNECT, but there the parameter is a path// that starts with a slash. It can be parsed with the regular URL parser,// and the path will end up in req.URL.Path, where it needs to be in order for// RPC to work.//處理Method == "CONNECT" 的具體情況justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")if justAuthority {rawurl = "http://" + rawurl} //HTTP URL解析if req.URL, err = url.ParseRequestURI(rawurl); err != nil {return nil, err}if justAuthority {// Strip the bogus "http://" back off.req.URL.Scheme = ""}// Subsequent lines: Key: value.mimeHeader, err := tp.ReadMIMEHeader() //HTTP Header中的key-value資料if err != nil {return nil, err}req.Header = Header(mimeHeader)// RFC2616: Must treat//GET /index.html HTTP/1.1//Host: www.google.com// and//GET http://www.google.com/index.html HTTP/1.1//Host: doesntmatter// the same. In the second case, any Host line is ignored.req.Host = req.URL.Hostif req.Host == "" {req.Host = req.Header.get("Host")}delete(req.Header, "Host") //把Header裡面的map中的Host去掉fixPragmaCacheControl(req.Header) //設定Cache-Control欄位err = readTransfer(req, b)if err != nil {return nil, err}req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false) //是否需要關閉return req, nil}
ParseHTTPVersion(解析HTTP的ProtoMajor 和 ProtoMinor)
func ParseHTTPVersion(vers string) (major, minor int, ok bool) {const Big = 1000000 // arbitrary upper bound switch vers {case "HTTP/1.1":return 1, 1, truecase "HTTP/1.0":return 1, 0, true}if !strings.HasPrefix(vers, "HTTP/") {return 0, 0, false}dot := strings.Index(vers, ".")if dot < 0 {return 0, 0, false}major, err := strconv.Atoi(vers[5:dot])if err != nil || major < 0 || major > Big {return 0, 0, false}minor, err = strconv.Atoi(vers[dot+1:])if err != nil || minor < 0 || minor > Big {return 0, 0, false}return major, minor, true}
設定 Cache-Control欄位
func fixPragmaCacheControl(header Header) {if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {if _, presentcc := header["Cache-Control"]; !presentcc {header["Cache-Control"] = []string{"no-cache"}}}}
是否需要關閉
func shouldClose(major, minor int, header Header, removeCloseHeader bool) bool {if major < 1 {return true} else if major == 1 && minor == 0 { //這種情況判斷長串連if !strings.Contains(strings.ToLower(header.get("Connection")), "keep-alive") {return true}return false} else {// TODO: Should split on commas, toss surrounding white space,// and check each field. //判斷Connection欄位if strings.ToLower(header.get("Connection")) == "close" {if removeCloseHeader {header.Del("Connection")}return true}}return false}
比較長的區分Request和Response的readTransfer方法
// msg is *Request or *Response.func readTransfer(msg interface{}, r *bufio.Reader) (err error) {t := &transferReader{RequestMethod: "GET"}// Unify inputisResponse := falseswitch rr := msg.(type) {case *Response:t.Header = rr.Header t.StatusCode = rr.StatusCodet.ProtoMajor = rr.ProtoMajort.ProtoMinor = rr.ProtoMinort.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header, true)isResponse = trueif rr.Request != nil {t.RequestMethod = rr.Request.Method}case *Request:t.Header = rr.Headert.ProtoMajor = rr.ProtoMajort.ProtoMinor = rr.ProtoMinor// Transfer semantics for Requests are exactly like those for// Responses with status code 200, responding to a GET methodt.StatusCode = 200default:panic("unexpected type")}// Default to HTTP/1.1if t.ProtoMajor == 0 && t.ProtoMinor == 0 {t.ProtoMajor, t.ProtoMinor = 1, 1}// Transfer encoding, content lengtht.TransferEncoding, err = fixTransferEncoding(t.RequestMethod, t.Header)if err != nil {return err}realLength, err := fixLength(isResponse, t.StatusCode, t.RequestMethod, t.Header, t.TransferEncoding)if err != nil {return err}if isResponse && t.RequestMethod == "HEAD" {if n, err := parseContentLength(t.Header.get("Content-Length")); err != nil {return err} else {t.ContentLength = n}} else {t.ContentLength = realLength}// Trailert.Trailer, err = fixTrailer(t.Header, t.TransferEncoding)if err != nil {return err}// If there is no Content-Length or chunked Transfer-Encoding on a *Response// and the status is not 1xx, 204 or 304, then the body is unbounded.// See RFC2616, section 4.4.switch msg.(type) {case *Response:if realLength == -1 &&!chunked(t.TransferEncoding) &&bodyAllowedForStatus(t.StatusCode) {// Unbounded body.t.Close = true}}// Prepare body reader. ContentLength < 0 means chunked encoding// or close connection when finished, since multipart is not supported yet//注意這裡ContentLength < 0 表示 chunked encoding 或者已經接受完資料,關閉串連switch {case chunked(t.TransferEncoding):if noBodyExpected(t.RequestMethod) {t.Body = eofReader} else {t.Body = &body{src: internal.NewChunkedReader(r), hdr: msg, r: r, closing: t.Close}}case realLength == 0:t.Body = eofReadercase realLength > 0:t.Body = &body{src: io.LimitReader(r, realLength), closing: t.Close}default:// realLength < 0, i.e. "Content-Length" not mentioned in headerif t.Close {// Close semantics (i.e. HTTP/1.0)t.Body = &body{src: r, closing: t.Close}} else {// Persistent connection (i.e. HTTP/1.1)t.Body = eofReader}}// Unify outputswitch rr := msg.(type) {case *Request:rr.Body = t.Bodyrr.ContentLength = t.ContentLengthrr.TransferEncoding = t.TransferEncodingrr.Close = t.Closerr.Trailer = t.Trailercase *Response:rr.Body = t.Bodyrr.ContentLength = t.ContentLengthrr.TransferEncoding = t.TransferEncodingrr.Close = t.Closerr.Trailer = t.Trailer}return nil}
總結:如果自己想動手寫HTTP伺服器,這些一些基礎性的處理工作可以借鑒一下