implemented http server

This commit is contained in:
Altair-sh
2026-11-02 21:47:34 +01:00
parent e0cf8a12a9
commit bac4ccb34e
14 changed files with 775 additions and 178 deletions
+17 -96
View File
@@ -2,26 +2,32 @@ package server
import (
"errors"
"io"
"net"
"os"
"serveg/internal/http"
"serveg/internal/log"
"strconv"
"sync"
"syscall"
"time"
)
type Server struct {
Conf *Config
Conf *Config
logger log.LoggerContext
// used in sockets to sed read/write deadline
timeout time.Duration
listener net.Listener
logger log.LoggerContext
wg sync.WaitGroup
}
const defaultTimeout = 30 * time.Second
func NewServer(conf *Config) *Server {
return &Server{
Conf: conf,
logger: log.LoggerContext{Name: "server"},
timeout: defaultTimeout,
}
}
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 {
@@ -33,7 +39,7 @@ func (srv *Server) StartGoroutine() (err error) {
func (srv *Server) serve() {
srv.logger.Info("server started")
for i := int64(0); ; i++ {
conn, err := srv.listener.Accept()
accepted, err := srv.listener.Accept()
if err != nil {
if errors.Is(err, net.ErrClosed) {
srv.logger.Info("listener socket closed")
@@ -42,9 +48,8 @@ func (srv *Server) serve() {
srv.logger.Warn(err)
continue
}
// TODO: add connection to map. SetDeadline(Now()) in Stop().
// srv.wg.Go(func() { handleConnection(conn.(*net.TCPConn), i) })
go handleConnection(conn.(*net.TCPConn), i)
conn := newConnection(srv, i, accepted.(*net.TCPConn))
go conn.handle()
}
}
@@ -53,87 +58,3 @@ func (srv *Server) Stop() {
_ = 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)
}
_ = socket.Close()
logger.Info("connection closed")
}()
conn := http.NewConnection(socket)
for {
err := socket.SetDeadline(time.Now().Add(30 * time.Second))
if err != nil {
logger.Warn(err)
return
}
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))
}