made webserver path configurable

This commit is contained in:
Jan Halfar 2018-11-27 13:28:20 +01:00
parent 37147120e2
commit f96a5669a8
5 changed files with 187 additions and 163 deletions

View File

@ -15,7 +15,7 @@ import (
"github.com/foomo/contentserver/server"
)
var testServerIsRunning = false
const pathContentserver = "/contentserver"
func dump(t *testing.T, v interface{}) {
jsonBytes, err := json.MarshalIndent(v, "", " ")
@ -51,7 +51,13 @@ func initTestServer(t testing.TB) (socketAddr, webserverAddr string) {
webserverAddr = getAvailableAddr()
testServer, varDir := mock.GetMockData(t)
log.SelectedLevel = log.LevelError
go server.RunServerSocketAndWebServer(testServer.URL+"/repo-two-dimensions.json", socketAddr, webserverAddr, varDir)
go server.RunServerSocketAndWebServer(
testServer.URL+"/repo-two-dimensions.json",
socketAddr,
webserverAddr,
pathContentserver,
varDir,
)
socketClient, errClient := NewClient(socketAddr, 1, time.Duration(time.Millisecond*100))
if errClient != nil {
panic(errClient)
@ -80,12 +86,12 @@ func getTestClients(t testing.TB) (socketClient *Client, httpClient *Client) {
testServerSocketAddr = socketAddr
testServerWebserverAddr = webserverAddr
}
socketClient, errClient := NewClient(testServerSocketAddr, 30, time.Duration(time.Millisecond*100))
socketClient, errClient := NewClient(testServerSocketAddr, 25, time.Duration(time.Millisecond*100))
if errClient != nil {
t.Log(errClient)
t.Fail()
}
httpClient, errHTTPClient := NewHTTPClient("http://" + testServerWebserverAddr + server.PathContentserver)
httpClient, errHTTPClient := NewHTTPClient("http://" + testServerWebserverAddr + pathContentserver)
if errHTTPClient != nil {
t.Log(errHTTPClient)
t.Fail()

View File

@ -21,7 +21,9 @@ const (
var (
uniqushPushVersion = "content-server 1.4.0"
showVersionFlag = flag.Bool("version", false, "version info")
address = flag.String("address", "127.0.0.1:8081", "address to bind host:port")
address = flag.String("address", "127.0.0.1:8081", "address to bind socket server host:port")
webserverAddress = flag.String("webserver-address", "", "address to bind web server host:port, when empty no webserver will be spawned")
webserverPath = flag.String("webserver-path", "/contentserver", "path to export the webserver on - useful when behind a proxy")
varDir = flag.String("var-dir", "/var/lib/contentserver", "where to put my data")
logLevelOptions = []string{
logLevelError,
@ -41,7 +43,7 @@ var (
)
func exitUsage(code int) {
fmt.Printf("Usage: %s http(s)://your-content-server/path/to/content.json\n", os.Args[0])
fmt.Println("Usage:", os.Args[0], "http(s)://your-content-server/path/to/content.json")
flag.PrintDefaults()
os.Exit(code)
}
@ -68,7 +70,7 @@ func main() {
level = log.LevelDebug
}
log.SelectedLevel = level
err := server.Run(flag.Arg(0), *address, *varDir)
err := server.RunServerSocketAndWebServer(flag.Arg(0), *address, *webserverAddress, *webserverPath, *varDir)
if err != nil {
fmt.Println("exiting with error", err)
os.Exit(1)

View File

@ -5,12 +5,9 @@ import (
"fmt"
"net"
"net/http"
"strconv"
"strings"
"github.com/foomo/contentserver/log"
"github.com/foomo/contentserver/repo"
"github.com/foomo/contentserver/responses"
)
// Handler type
@ -29,158 +26,16 @@ const (
HandlerGetRepo = "getRepo"
)
// simple internal request counter
type stats struct {
requests int64
chanCount chan int
}
func newStats() *stats {
s := &stats{
requests: 0,
chanCount: make(chan int),
}
go func() {
for {
select {
case <-s.chanCount:
s.requests++
s.chanCount <- 1
}
}
}()
return s
}
func (s *stats) countRequest() {
s.chanCount <- 1
<-s.chanCount
}
type socketServer struct {
stats *stats
repo *repo.Repo
}
func extractHandlerAndJSONLentgh(header string) (handler Handler, jsonLength int, err error) {
headerParts := strings.Split(header, ":")
if len(headerParts) != 2 {
return "", 0, errors.New("invalid header")
}
jsonLength, err = strconv.Atoi(headerParts[1])
if err != nil {
err = fmt.Errorf("could not parse length in header: %q", header)
}
return Handler(headerParts[0]), jsonLength, err
}
func (s *socketServer) execute(handler Handler, jsonBytes []byte) (reply []byte) {
s.stats.countRequest()
log.Notice("socketServer.execute: ", s.stats.requests, ", ", handler)
if log.SelectedLevel == log.LevelDebug {
log.Debug(" incoming json buffer:", string(jsonBytes))
}
reply, handlingError := handleRequest(s.repo, handler, jsonBytes)
if handlingError != nil {
log.Error("socketServer.execute handlingError :", handlingError)
}
return reply
}
func (s *socketServer) writeResponse(conn net.Conn, reply []byte) {
headerBytes := []byte(strconv.Itoa(len(reply)))
reply = append(headerBytes, reply...)
log.Debug(" replying: " + string(reply))
n, writeError := conn.Write(reply)
if writeError != nil {
log.Error("socketServer.writeResponse: could not write my reply: " + fmt.Sprint(writeError))
return
}
if n < len(reply) {
log.Error(fmt.Sprintf("socketServer.writeResponse: write too short %q instead of %q", n, len(reply)))
return
}
log.Debug(" replied. waiting for next request on open connection")
}
func (s *socketServer) handleConnection(conn net.Conn) {
log.Debug("socketServer.handleConnection")
var headerBuffer [1]byte
header := ""
i := 0
for {
i++
// fmt.Println("---->", i)
// let us read with 1 byte steps on conn until we find "{"
_, readErr := conn.Read(headerBuffer[0:])
if readErr != nil {
log.Debug(" looks like the client closed the connection: ", readErr)
return
}
// read next byte
current := headerBuffer[0:]
if string(current) == "{" {
// json has started
handler, jsonLength, headerErr := extractHandlerAndJSONLentgh(header)
// reset header
header = ""
if headerErr != nil {
log.Error("invalid request could not read header", headerErr)
encodedErr, encodingErr := encodeReply(responses.NewError(4, "invalid header "+headerErr.Error()))
if encodingErr == nil {
s.writeResponse(conn, encodedErr)
} else {
log.Error("could not respond to invalid request", encodingErr)
}
return
}
log.Debug(fmt.Sprintf(" found json with %d bytes", jsonLength))
if jsonLength > 0 {
// let us try to read some json
jsonBytes := make([]byte, jsonLength)
// that is "{"
jsonBytes[0] = 123
jsonLengthCurrent := 1
readRound := 0
for jsonLengthCurrent < jsonLength {
readRound++
readLength, jsonReadErr := conn.Read(jsonBytes[jsonLengthCurrent:jsonLength])
if jsonReadErr != nil {
//@fixme we need to force a read timeout (SetReadDeadline?), if expected jsonLength is lower than really sent bytes (e.g. if client implements protocol wrong)
//@todo should we check for io.EOF here
log.Error(" could not read json - giving up with this client connection" + fmt.Sprint(jsonReadErr))
return
}
jsonLengthCurrent += readLength
log.Debug(fmt.Sprintf(" read so far %d of %d bytes in read cycle %d", jsonLengthCurrent, jsonLength, readRound))
}
if log.SelectedLevel == log.LevelDebug {
log.Debug(" read json: " + string(jsonBytes))
}
s.writeResponse(conn, s.execute(handler, jsonBytes))
// note: connection remains open
continue
}
log.Error("can not read empty json")
return
}
// adding to header byte by byte
header += string(headerBuffer[0:])
}
}
// Run - let it run and enjoy on a socket near you
func Run(server string, address string, varDir string) error {
return RunServerSocketAndWebServer(server, address, "", varDir)
return RunServerSocketAndWebServer(server, address, "", "", varDir)
}
func RunServerSocketAndWebServer(
server string,
address string,
webserverAdresss string,
webserverPath string,
varDir string,
) error {
if address == "" && webserverAdresss == "" {
@ -195,7 +50,7 @@ func RunServerSocketAndWebServer(
go runSocketServer(r, address, chanErr)
}
if webserverAdresss != "" {
go runWebserver(r, webserverAdresss, chanErr)
go runWebserver(r, webserverAdresss, webserverPath, chanErr)
}
return <-chanErr
}
@ -203,10 +58,13 @@ func RunServerSocketAndWebServer(
func runWebserver(
r *repo.Repo,
address string,
path string,
chanErr chan error,
) {
s := &webServer{
r: r,
s, errNew := NewWebServer(path, r)
if errNew != nil {
chanErr <- errNew
return
}
chanErr <- http.ListenAndServe(address, s)
}

155
server/socketserver.go Normal file
View File

@ -0,0 +1,155 @@
package server
import (
"errors"
"fmt"
"net"
"strconv"
"strings"
"github.com/foomo/contentserver/log"
"github.com/foomo/contentserver/repo"
"github.com/foomo/contentserver/responses"
)
// simple internal request counter
type stats struct {
requests int64
chanCount chan int
}
func newStats() *stats {
s := &stats{
requests: 0,
chanCount: make(chan int),
}
go func() {
for {
select {
case <-s.chanCount:
s.requests++
s.chanCount <- 1
}
}
}()
return s
}
func (s *stats) countRequest() {
s.chanCount <- 1
<-s.chanCount
}
type socketServer struct {
stats *stats
repo *repo.Repo
}
func extractHandlerAndJSONLentgh(header string) (handler Handler, jsonLength int, err error) {
headerParts := strings.Split(header, ":")
if len(headerParts) != 2 {
return "", 0, errors.New("invalid header")
}
jsonLength, err = strconv.Atoi(headerParts[1])
if err != nil {
err = fmt.Errorf("could not parse length in header: %q", header)
}
return Handler(headerParts[0]), jsonLength, err
}
func (s *socketServer) execute(handler Handler, jsonBytes []byte) (reply []byte) {
s.stats.countRequest()
log.Notice("socketServer.execute: ", s.stats.requests, ", ", handler)
if log.SelectedLevel == log.LevelDebug {
log.Debug(" incoming json buffer:", string(jsonBytes))
}
reply, handlingError := handleRequest(s.repo, handler, jsonBytes)
if handlingError != nil {
log.Error("socketServer.execute handlingError :", handlingError)
}
return reply
}
func (s *socketServer) writeResponse(conn net.Conn, reply []byte) {
headerBytes := []byte(strconv.Itoa(len(reply)))
reply = append(headerBytes, reply...)
log.Debug(" replying: " + string(reply))
n, writeError := conn.Write(reply)
if writeError != nil {
log.Error("socketServer.writeResponse: could not write my reply: " + fmt.Sprint(writeError))
return
}
if n < len(reply) {
log.Error(fmt.Sprintf("socketServer.writeResponse: write too short %q instead of %q", n, len(reply)))
return
}
log.Debug(" replied. waiting for next request on open connection")
}
func (s *socketServer) handleConnection(conn net.Conn) {
log.Debug("socketServer.handleConnection")
var headerBuffer [1]byte
header := ""
i := 0
for {
i++
// fmt.Println("---->", i)
// let us read with 1 byte steps on conn until we find "{"
_, readErr := conn.Read(headerBuffer[0:])
if readErr != nil {
log.Debug(" looks like the client closed the connection: ", readErr)
return
}
// read next byte
current := headerBuffer[0:]
if string(current) == "{" {
// json has started
handler, jsonLength, headerErr := extractHandlerAndJSONLentgh(header)
// reset header
header = ""
if headerErr != nil {
log.Error("invalid request could not read header", headerErr)
encodedErr, encodingErr := encodeReply(responses.NewError(4, "invalid header "+headerErr.Error()))
if encodingErr == nil {
s.writeResponse(conn, encodedErr)
} else {
log.Error("could not respond to invalid request", encodingErr)
}
return
}
log.Debug(fmt.Sprintf(" found json with %d bytes", jsonLength))
if jsonLength > 0 {
// let us try to read some json
jsonBytes := make([]byte, jsonLength)
// that is "{"
jsonBytes[0] = 123
jsonLengthCurrent := 1
readRound := 0
for jsonLengthCurrent < jsonLength {
readRound++
readLength, jsonReadErr := conn.Read(jsonBytes[jsonLengthCurrent:jsonLength])
if jsonReadErr != nil {
//@fixme we need to force a read timeout (SetReadDeadline?), if expected jsonLength is lower than really sent bytes (e.g. if client implements protocol wrong)
//@todo should we check for io.EOF here
log.Error(" could not read json - giving up with this client connection" + fmt.Sprint(jsonReadErr))
return
}
jsonLengthCurrent += readLength
log.Debug(fmt.Sprintf(" read so far %d of %d bytes in read cycle %d", jsonLengthCurrent, jsonLength, readRound))
}
if log.SelectedLevel == log.LevelDebug {
log.Debug(" read json: " + string(jsonBytes))
}
s.writeResponse(conn, s.execute(handler, jsonBytes))
// note: connection remains open
continue
}
log.Error("can not read empty json")
return
}
// adding to header byte by byte
header += string(headerBuffer[0:])
}
}

View File

@ -8,14 +8,17 @@ import (
"github.com/foomo/contentserver/repo"
)
const PathContentserver = "/contentserver"
type webServer struct {
r *repo.Repo
r *repo.Repo
path string
}
func newWebServer() (s *webServer, err error) {
s = &webServer{}
// NewWebServer returns a shiny new web server
func NewWebServer(path string, r *repo.Repo) (s http.Handler, err error) {
s = &webServer{
r: r,
path: path,
}
return
}
@ -30,7 +33,7 @@ func (s *webServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "failed to read incoming request", http.StatusBadRequest)
return
}
reply, errReply := handleRequest(s.r, Handler(strings.TrimPrefix(r.URL.Path, PathContentserver+"/")), jsonBytes)
reply, errReply := handleRequest(s.r, Handler(strings.TrimPrefix(r.URL.Path, s.path+"/")), jsonBytes)
if errReply != nil {
http.Error(w, errReply.Error(), http.StatusInternalServerError)
return