// Copyright (c) 2022 Nadeen Udantha . All rights reserved. package boihttp import ( "bufio" "bytes" "io" "strconv" ) const ( VersionHTTP1_0 = "HTTP/1.0" VersionHTTP1_1 = "HTTP/1.1" ) // https://datatracker.ietf.org/doc/html/rfc2616#section-4 type Message struct { StatusCode int Version, Method, Path, Status string Headers } func (m *Message) SetStatus(code int, status string) { m.Status = status m.StatusCode = code } var EmptyBody io.Reader = &emptyBody{} type emptyBody struct{} func (l *emptyBody) Read(p []byte) (n int, err error) { return 0, io.EOF } func (m *Message) body(r *bufio.Reader) io.Reader { n := m.ContentLength() if n == 0 { return EmptyBody } return &io.LimitedReader{R: r, N: int64(n)} } func (m *Message) ReadRequest(r *bufio.Reader) error { return m.read(r, true) } func (m *Message) ReadResponse(r *bufio.Reader) error { return m.read(r, false) } func (m *Message) read(r *bufio.Reader, isRequest bool) error { line, err := readline(r) if err != nil { return err } parts := bytes.SplitN(line, []byte(" "), 3) if len(parts) != 3 { panic("wtf?") } if isRequest { // GET / HTTP/1.0 m.Method, m.Path, m.Version = string(parts[0]), string(parts[1]), string(parts[2]) } else { // HTTP/1.0 200 Noice m.StatusCode, err = strconv.Atoi(string(parts[1])) if err != nil { return err } m.Version, m.Status = string(parts[0]), string(parts[2]) } m.Headers = make(Headers) n := 0 for { line, err := readline(r) if err != nil { return err } if len(line) == 0 { break } parts = bytes.SplitN(line, []byte(": "), 2) if len(parts) != 2 { panic("wtf") } m.Add(string(parts[0]), string(parts[1])) n++ if n >= 64 { panic("wtf?") } } return nil } func readline(r *bufio.Reader) ([]byte, error) { line, p, err := r.ReadLine() if p || len(line) > 8192 { panic("wtf?") } return line, err } const crlf = "\r\n" func (m *Message) WriteRequest(r *bufio.Writer) error { return m.write(r, true) } func (m *Message) WriteResponse(r *bufio.Writer) error { return m.write(r, false) } func (m *Message) write(w *bufio.Writer, isRequest bool) error { if isRequest { // GET / HTTP/1.0 w.WriteString(m.Method) w.WriteByte(' ') w.WriteString(m.Path) w.WriteByte(' ') w.WriteString(m.Version) } else { // HTTP/1.0 200 Noice w.WriteString(m.Version) w.WriteByte(' ') w.WriteString(strconv.Itoa(m.StatusCode)) w.WriteByte(' ') w.WriteString(m.Status) } w.WriteString(crlf) for k, vs := range m.Headers { for _, v := range vs { w.WriteString(k) w.WriteString(": ") w.WriteString(v) w.WriteString(crlf) } } _, err := w.WriteString(crlf) return err }