How does the server parse into a specific request object after receiving the HTTP data sent by the client? Let's see how Golang is handled.
Let's start with a concrete example: just Method,url and Proto inside HTTP
package mainimport ("Bufio" "FMT" _ "io" "net/http" "Net/textproto" "Strings") Func main () {request ()}//Of course This example is relatively simple, just through \ n to parse out a few fields 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}//--------------------------------output is://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>}
take a closer look at Golang http source
Func readrequest (B *bufio. Reader) (req *request, err error) {tp := newtextprotoreader (b) // Encapsulates a reader structure req = new (Request)// first line: get /index.html http/1.0 //the parsing var s stringif s, err = tp of the first line of information. ReadLine (); err != nil {return nil, err}defer func () { Puttextprotoreader (TP) If err == io. Eof {err = io. Errunexpectedeof}} () var ok bool //parsing req of the first line of information. Method, req. Requesturi, req. Proto, ok = parserequestline (s) if !ok {return nil, &badstringerror{" Malformed http request ", s}}rawurl := req. Requesturi //client the requested url//http corresponds to Protomajor and protominorif req. Protomajor, req. Protominor, ok = parsehttpversion (req. Proto); !ok {return nil, &badstringerror{"Malformed&nbSp 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.//handle the method == "CONNECT" details justauthority := req. method == "CONNECT" && !strings. Hasprefix (rawurl, "/") if justauthority {rawurl = "/http" + rawurl} //http url Parsing if req. Url, err = url. Parserequesturi (Rawurl); err != nil {return nil, err}if justauthority {// strip the bogus "/http" back off.req.URL.Scheme = ""}// SUBSEQUENT&NBSP;LINES:&NBSP;KEY:&NBSP;VALUE.MIMEHEADER,&NBSP;ERR&NBSP;:=&NBSP;TP. Readmimeheader () key-value data if err != nil {return nil, in //http header 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") //remove the host from the map in the header Fixpragmacachecontrol (req. Header) //set Cache-control field Err = readtransfer (req, b) if err != nil { Return nil, err}req. Close = shouldclose (req. Protomajor, req. Protominor, req. Header, false) //If you need to close Return req, nil}
parsehttpversion (resolves http protomajor and 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
Setting the Cache-control field
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"}}}}
Whether you need to close
Func Shouldclose (major, minor int, header header, removecloseheader bool) bool {if major < 1 {return true} else if Majo r = = 1 && Minor = = 0 {//This condition determines long connection if!strings. Contains (Strings. ToLower (Header.get ("Connection")), "Keep-alive") {return True}return false} else {//Todo:should split on commas, toss Su Rrounding White space,//and check each field. Judge the connection field if strings. ToLower (Header.get ("Connection")) = = "Close" {if Removecloseheader {header. Del ("Connection")}return True}}return false}
A long Readtransfer method for distinguishing between request and response
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&nbSp;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&NBSP;ON&NBSP;A&NBSP;*RESPONSE//&NBSP;AND&NBSP;THE&NBSP;STATUS&NBSP;IS&NBSP;NOT&NBSP;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//Notice here contentlength < 0 represents chunked encoding or has finished receiving data, close connection 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}
Summary: If you want to write the HTTP server, these basic processing work can draw on
Golang HTTP Readrequest