Files
dolt/go/datas/database_server.go
Dan Willhite 46586ee928 Remove msg args from d.PanicIfTrue and d.PanicIfFalse. (#2757)
Should discourage people from writing code that does unnecessary work
to generate a msg every time that an error condition is checked. Fixes #2741
2016-11-03 11:43:57 -07:00

138 lines
4.1 KiB
Go

// Copyright 2016 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
package datas
import (
"fmt"
"net"
"net/http"
"strconv"
"github.com/attic-labs/noms/go/chunks"
"github.com/attic-labs/noms/go/constants"
"github.com/attic-labs/noms/go/d"
"github.com/julienschmidt/httprouter"
)
type connectionState struct {
c net.Conn
cs http.ConnState
}
type RemoteDatabaseServer struct {
cs chunks.ChunkStore
port int
l *net.Listener
csChan chan *connectionState
closing bool
// Called just before the server is started.
Ready func()
}
func NewRemoteDatabaseServer(cs chunks.ChunkStore, port int) *RemoteDatabaseServer {
dataVersion := cs.Version()
if constants.NomsVersion != dataVersion {
d.Panic("SDK version %s is incompatible with data of version %s", constants.NomsVersion, dataVersion)
}
return &RemoteDatabaseServer{
cs, port, nil, make(chan *connectionState, 16), false, func() {},
}
}
// Port is the actual port used. This may be different than the port passed in to NewRemoteDatabaseServer.
func (s *RemoteDatabaseServer) Port() int {
return s.port
}
// Run blocks while the RemoteDatabaseServer is listening. Running on a separate go routine is supported.
func (s *RemoteDatabaseServer) Run() {
l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.port))
d.Chk.NoError(err)
s.l = &l
_, port, err := net.SplitHostPort(l.Addr().String())
d.Chk.NoError(err)
s.port, err = strconv.Atoi(port)
d.Chk.NoError(err)
fmt.Printf("Listening on port %d...\n", s.port)
router := httprouter.New()
router.POST(constants.GetRefsPath, s.corsHandle(s.makeHandle(HandleGetRefs)))
router.OPTIONS(constants.GetRefsPath, s.corsHandle(noopHandle))
router.POST(constants.HasRefsPath, s.corsHandle(s.makeHandle(HandleHasRefs)))
router.OPTIONS(constants.HasRefsPath, s.corsHandle(noopHandle))
router.GET(constants.RootPath, s.corsHandle(s.makeHandle(HandleRootGet)))
router.POST(constants.RootPath, s.corsHandle(s.makeHandle(HandleRootPost)))
router.OPTIONS(constants.RootPath, s.corsHandle(noopHandle))
router.POST(constants.WriteValuePath, s.corsHandle(s.makeHandle(HandleWriteValue)))
router.OPTIONS(constants.WriteValuePath, s.corsHandle(noopHandle))
router.GET(constants.BasePath, s.corsHandle(s.makeHandle(HandleBaseGet)))
srv := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
router.ServeHTTP(w, req)
}),
ConnState: s.connState,
}
go func() {
m := map[net.Conn]http.ConnState{}
for connState := range s.csChan {
switch connState.cs {
case http.StateNew, http.StateActive, http.StateIdle:
m[connState.c] = connState.cs
default:
delete(m, connState.c)
}
}
for c := range m {
c.Close()
}
}()
go s.Ready()
srv.Serve(l)
}
func (s *RemoteDatabaseServer) makeHandle(hndlr Handler) httprouter.Handle {
return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
hndlr(w, req, ps, s.cs)
}
}
func noopHandle(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
}
func (s *RemoteDatabaseServer) corsHandle(f httprouter.Handle) httprouter.Handle {
// TODO: Implement full pre-flighting?
// See: http://www.html5rocks.com/static/images/cors_server_flowchart.png
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// Can't use * when clients are using cookies.
w.Header().Add("Access-Control-Allow-Origin", r.Header.Get("Origin"))
w.Header().Add("Access-Control-Allow-Methods", "GET, POST")
w.Header().Add("Access-Control-Allow-Headers", NomsVersionHeader)
w.Header().Add("Access-Control-Expose-Headers", NomsVersionHeader)
w.Header().Add(NomsVersionHeader, constants.NomsVersion)
f(w, r, ps)
}
}
func (s *RemoteDatabaseServer) connState(c net.Conn, cs http.ConnState) {
if s.closing {
d.PanicIfFalse(cs == http.StateClosed)
return
}
s.csChan <- &connectionState{c, cs}
}
// Will cause the RemoteDatabaseServer to stop listening and an existing call to Run() to continue.
func (s *RemoteDatabaseServer) Stop() {
s.closing = true
(*s.l).Close()
(s.cs).Close()
close(s.csChan)
}