114 lines
2.1 KiB
Go
114 lines
2.1 KiB
Go
package httpserver
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type TestServer struct {
|
|
*httptest.Server
|
|
mu sync.Mutex
|
|
routes map[string]*Response
|
|
stats map[string]*Stat
|
|
}
|
|
|
|
func NewTestServer(routes map[string]*Response) *TestServer {
|
|
ts := &TestServer{
|
|
routes: routes,
|
|
stats: map[string]*Stat{},
|
|
}
|
|
ts.Server = httptest.NewServer(ts)
|
|
return ts
|
|
}
|
|
|
|
func (s *TestServer) SetRoute(name string, resp *Response) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.routes[name] = resp
|
|
}
|
|
|
|
func (s *TestServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
s.mu.Lock()
|
|
resp, ok := s.routes[r.URL.Path]
|
|
if !ok {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
s.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
if _, ok := s.stats[r.URL.Path]; !ok {
|
|
s.stats[r.URL.Path] = &Stat{}
|
|
}
|
|
|
|
s.stats[r.URL.Path].AllRequests++
|
|
s.stats[r.URL.Path].Requests = append(s.stats[r.URL.Path].Requests, newRequest(r))
|
|
|
|
if resp.LastModified != nil {
|
|
w.Header().Set("Last-Modified", resp.LastModified.Format(time.RFC850))
|
|
}
|
|
|
|
if resp.ContentEncoding != "" {
|
|
w.Header().Set("Content-Encoding", resp.ContentEncoding)
|
|
}
|
|
|
|
if resp.ContentDisposition != "" {
|
|
w.Header().Set("Content-Disposition", resp.ContentDisposition)
|
|
}
|
|
|
|
if resp.Etag != "" {
|
|
w.Header().Set("ETag", resp.Etag)
|
|
if match := r.Header.Get("If-None-Match"); match == resp.Etag {
|
|
w.WriteHeader(http.StatusNotModified)
|
|
s.stats[r.URL.Path].CachedRequests++
|
|
s.mu.Unlock()
|
|
return
|
|
}
|
|
}
|
|
|
|
s.mu.Unlock()
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
io.Copy(w, bytes.NewReader(resp.Content))
|
|
}
|
|
|
|
func (s *TestServer) Stats(name string) (st Stat) {
|
|
if st, ok := s.stats[name]; ok {
|
|
return *st
|
|
}
|
|
return
|
|
}
|
|
|
|
type Response struct {
|
|
Content []byte
|
|
Etag string
|
|
LastModified *time.Time
|
|
ContentEncoding string
|
|
ContentDisposition string
|
|
}
|
|
|
|
type Stat struct {
|
|
AllRequests, CachedRequests int
|
|
Requests []Request
|
|
}
|
|
|
|
type Request struct {
|
|
Method string
|
|
Header http.Header
|
|
}
|
|
|
|
func newRequest(r *http.Request) Request {
|
|
headers := http.Header{}
|
|
for k, v := range r.Header {
|
|
headers[k] = slices.Clone(v)
|
|
}
|
|
return Request{
|
|
Method: r.Method,
|
|
Header: headers,
|
|
}
|
|
}
|