mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-23 18:58:50 -06:00
Should discourage people from writing code that does unnecessary work to generate a msg every time that an error condition is checked. Fixes #2741
138 lines
4.1 KiB
Go
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)
|
|
}
|