implemented HTTP 1.0/1.1 server
This commit is contained in:
@@ -1,23 +1,21 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
// TODO: add example config file
|
||||
type Config struct {
|
||||
Port uint16 `toml:"port"`
|
||||
Ip IP `toml:"ip"`
|
||||
Addr string `toml:"address"`
|
||||
}
|
||||
|
||||
func ReadConfig(path string) (*Config, error) {
|
||||
var config = new(Config)
|
||||
var config = DefaultConfig()
|
||||
tomlMeta, err := toml.DecodeFile(path, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -33,8 +31,7 @@ func ReadConfig(path string) (*Config, error) {
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Port: 8080,
|
||||
Ip: IP{net.IPv4(127, 0, 0, 1)},
|
||||
Addr: "127.0.0.1:8080",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,20 +59,3 @@ func WriteConfig(config *Config, path string) (err error) {
|
||||
enc.Indent = " " // 4
|
||||
return enc.Encode(config)
|
||||
}
|
||||
|
||||
type IP struct {
|
||||
net.IP
|
||||
}
|
||||
|
||||
var _ encoding.TextUnmarshaler = (*IP)(nil)
|
||||
var _ encoding.TextMarshaler = IP{}
|
||||
|
||||
func (a *IP) UnmarshalText(text []byte) error {
|
||||
s := string(text)
|
||||
ip := net.ParseIP(s)
|
||||
if ip == nil {
|
||||
return errors.New("can't parse '" + s + "' as IP address")
|
||||
}
|
||||
a.IP = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
+106
-26
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user