HTTP server
Introduction
Setup Server
Using GoLang, it is easy to set up a HTTP server listening at localhost port 8000 by writing.
func main() {
r := RegisterHandlers()
mh := NewMiddlewareHandler(r, 20)
http.ListenAndServe(":8000", mh)
}
Handlers
Each request from the client-side will be forwarded to one of the handlers in "RegisterHandlers" function for processing based on the URI. When all handlers are registered, we wrap the handler with a "middlewareHandler". This middleware handler does nothing special but limits the number of concurrent connections to our file HTTP server. This is usually the case when we build file servers with images and videos, where too many connection request data simultaneously, "ConnLimiter" protects our server's network from jam and failure.
Currently, the connection limit is set to 20. We will increase this limit when we have a better server with a larger bandwidth
func RegisterHandlers() *httprouter.Router {
router := httprouter.New()
// upload, stream, picture
router.GET ("/api/v0/testpage", testPageHandler)
router.POST("/api/v0/upload/:vid-id", postVideoHandler)
router.GET("/api/v0/videos/:vid-id", streamHandler)
router.GET("/api/v0/images/:iid-id", pictureHandler)
// paint project
router.POST("/api/v2/post/:iid-id", postImageHandler)
router.GET("/api/v2/images/:iid-id", paintHandler)
// bird project (not used in this course)
router.GET("/api/v1/billboard", billboardHandler)
router.GET("/api/v1/dashboard", dashboardHandler)
return router
}
type middlewareHandler struct {
r *httprouter.Router
l *ConnLimiter
}
func NewMiddlewareHandler(r *httprouter.Router, cc int) http.Handler {
m := middlewareHandler{}
m.r = r
m.l = NewConnLimiter(cc)
return m
}
func (m *middlewareHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// check if go beyond our limit 20
if !m.l.GetConn() {
sendErrorResponse(w, http.StatusTooManyRequests, "Too many requests")
return
}
m.r.ServeHTTP(w, r)
defer m.l.ReleaseConn()
}
Implement a ConnLimiter
Below is a simple implementation of "ConnLimiter", basically it builds a channel as a bucket with size cc (20 in our case). If the bucket is not full, add a token 1 to our bucket and return true. Otherwise, return false. Hence, reject connection. "ReleaseConn" function on the other hand will remove a token from bucket, indicating a connection has finished. GoLang prefers CSP model for concurrency, so we use channel.
type ConnLimiter struct {
concurrentConn int
bucket chan int
}
func NewConnLimiter(cc int) *ConnLimiter {
return &ConnLimiter{
concurrentConn: cc,
bucket: make(chan int, cc),
}
}
func (cl *ConnLimiter) GetConn() bool {
if len(cl.bucket) >= cl.concurrentConn {
glog.Infoln("Reached the rate limitation")
return false
}
cl.bucket <- 1
return true
}
func (cl *ConnLimiter) ReleaseConn() {
c := <-cl.bucket
glog.Infof("A connection finished %d\n", c)
}
Handle Image Post
Images are sent via HTTP Post MultipartForm with the name "file", Unlike WebSocket, where we need to define our own protocol and message types, HTTP already defines those for us. Typically, we Post MultipartForm to send images. Later, we store the image with name provided by "Params(iid-id)" (In our case, it is the UUID generated by Android) on our server and replay to the client "UploadSuccessfully"
func postImageHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
r.Body = http.MaxBytesReader(w, r.Body, MAX_UPLOAD_SIZE)
if err := r.ParseMultipartForm(MAX_UPLOAD_SIZE); err != nil {
sendErrorResponse(w, http.StatusBadRequest, "File is too big") // 400
return
}
file, _, err := r.FormFile("file") // <form name = "file"
if err != nil {
sendErrorResponse(w, http.StatusInternalServerError, "Internal Error")
return
}
data, err := ioutil.ReadAll(file)
if err != nil {
glog.Errorf("Read file error: %v\n", err)
sendErrorResponse(w, http.StatusInternalServerError, "Internal Error")
}
fn := p.ByName("iid-id") + ".png"
err = ioutil.WriteFile(IMAGE_DIR + fn, data, 0666)
if err != nil {
glog.Errorf("Write file error: %v\n", err)
sendErrorResponse(w, http.StatusInternalServerError, "Internal Error")
return
}
w.WriteHeader(http.StatusCreated)
io.WriteString(w, "Uploaded Successfully")
}
func sendErrorResponse(w http.ResponseWriter, sc int, errMsg string) {
w.WriteHeader(sc)
io.WriteString(w, errMsg)
}
Handle Image Get
Find the file on server given name "Params(iid-id)", and send it to client via HTTP protocol.
func paintHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
iid := p.ByName("iid-id")
vl := IMAGE_DIR + iid
image, err := os.Open(vl)
if err != nil {
glog.Errorf("Error when open file: %v\n", err)
sendErrorResponse(w, http.StatusInternalServerError, "Internal Error") // 500
return
}
w.Header().Set("Content-Type", "image/png")
http.ServeContent(w, r, "", time.Now(), image)
defer image.Close()
}