implemented HTTP 1.0/1.1 server

This commit is contained in:
Altair-sh
2026-11-01 20:43:08 +01:00
parent 2de1f038c7
commit e0cf8a12a9
13 changed files with 956 additions and 68 deletions
+106 -26
View File
@@ -1,59 +1,139 @@
package server
import (
"errors"
"io"
"net"
"os"
"serveg/internal/http"
"serveg/internal/log"
"strconv"
"sync"
"syscall"
"time"
)
func Serve(conf *Config) error {
logger := log.LoggerContext{Name: "server"}
addr := net.JoinHostPort(conf.Ip.String(), strconv.Itoa(int(conf.Port)))
type Server struct {
Conf *Config
listener net.Listener
logger log.LoggerContext
wg sync.WaitGroup
}
logger.Info("listening at", addr)
srv, err := net.Listen("tcp", addr)
if err != nil {
return err
func (srv *Server) StartGoroutine() (err error) {
srv.logger = log.LoggerContext{Name: "server"}
srv.logger.Info("listening at ", srv.Conf.Addr)
srv.listener, err = net.Listen("tcp", srv.Conf.Addr)
if err == nil {
srv.wg.Go(srv.serve)
}
defer func() { _ = srv.Close() }()
return
}
func (srv *Server) serve() {
srv.logger.Info("server started")
for i := int64(0); ; i++ {
conn, err := srv.Accept()
conn, err := srv.listener.Accept()
if err != nil {
logger.Warn(err)
if errors.Is(err, net.ErrClosed) {
srv.logger.Info("listener socket closed")
return
}
srv.logger.Warn(err)
continue
}
go handleConnection(conn, i)
// TODO: add connection to map. SetDeadline(Now()) in Stop().
// srv.wg.Go(func() { handleConnection(conn.(*net.TCPConn), i) })
go handleConnection(conn.(*net.TCPConn), i)
}
}
func handleConnection(conn net.Conn, n int64) {
func (srv *Server) Stop() {
srv.logger.Info("stopping server")
_ = srv.listener.Close()
srv.wg.Wait()
}
func handleConnection(socket *net.TCPConn, n int64) {
logger := log.LoggerContext{Name: "conn-" + strconv.FormatInt(n, 10)}
defer func() {
if r := recover(); r != nil {
logger.Error("panic: ", r)
}
_ = conn.Close()
_ = socket.Close()
logger.Info("connection closed")
}()
buf := make([]byte, 4096)
conn := http.NewConnection(socket)
for {
if err := conn.SetDeadline(time.Now().Add(30 * time.Second)); err != nil {
logger.Warn(err)
break
}
n, err := conn.Read(buf)
err := socket.SetDeadline(time.Now().Add(30 * time.Second))
if err != nil {
logger.Warn(err)
break
return
}
msgBytes := buf[:n]
logger.Info("msg: `", string(msgBytes), "`")
if _, err := conn.Write(msgBytes); err != nil {
logger.Warn(err)
break
req, err := conn.ReadRequest()
if err != nil {
logConnError(logger, err)
if httpErr, ok := errors.AsType[*http.HttpError](err); ok {
// return error code to client
logger.Info("sending error response")
// req is partially parsed, may use fields
if err = conn.SendErrorResponse(httpErr, req, true); err != nil {
logger.Warn("can't send error response")
return
}
}
// close the connection
logger.Info("lingering close")
lingeringClose(socket)
return
}
logger.Info(req.Method, " ", req.Path, " ", req.Protocol,
"\nHeaders: ", req.Headers)
resBody := "Hi\n" + time.Now().Format(time.DateTime)
res := conn.CreateResponse(http.StatusOK, req, false)
res.AttachBodySmallStr(resBody, http.TextPlain)
logger.Info("sending response: ", res.Status,
"\nHeaders: ", res.Headers)
err = conn.SendResponse(res)
if err != nil {
logConnError(logger, err)
return
}
// it is set by CreateResponse
if res.Headers["connection"] == "close" {
logger.Info("lingering close")
lingeringClose(socket)
return
}
}
}
func logConnError(logger log.LoggerContext, err error) {
if errors.Is(err, os.ErrDeadlineExceeded) {
logger.Info("time out")
} else if errors.Is(err, io.EOF) ||
errors.Is(err, syscall.EPIPE) ||
errors.Is(err, syscall.ECONNRESET) {
logger.Info("closed by client")
} else {
logger.Warn(err)
}
}
// lingeringClose: shut down our side, then drain the client's unread data,
// so close() doesn't send RST and destroy the response before it's read.
// Limits: 500 ms, 32 KB
func lingeringClose(socket *net.TCPConn) {
if socket.CloseWrite() != nil {
return
}
_ = socket.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
_, _ = io.Copy(io.Discard, io.LimitReader(socket, 32*1024))
}