func (frontend *Frontend) getWebSocketHandler() func(*websocket.Conn) { return func(conn *websocket.Conn) { respStatus := http.StatusOK defer func() { conn.Close() frontend.Log(HTTPS_WEBSOCKET, respStatus, conn.Request.Host, conn.Request) }() request := make([]byte, 1024) for { n, err := conn.Read(request) if err != nil { if frontend.Debug { logging.Error("Error reading on WebSocket: %s", err) } break } response, status := getLiveItems(string(request[:n])) if status != http.StatusOK { respStatus = status break } if len(response) != 0 { if _, err = conn.Write(response); err != nil { break } } } } }
func getLiveItems(request string) ([]byte, int) { reqs := strings.Split(request, ",") if len(reqs) == 0 { return nil, http.StatusBadRequest } sid := reqs[0] /* XXX validate sid */ result, refresh, ok := pubsub.Listen(sid, reqs[1:], livequeryTimeout) if ok { data := make(map[string]map[string][]string) data["items"] = result response, err := json.Marshal(data) if err != nil { logging.Error("ERROR encoding JSON: %s for: %v", err, data) return nil, http.StatusInternalServerError } return response, http.StatusOK } refreshCount := len(refresh) if refreshCount != 0 { data := make(map[string][]string) qids := make([]string, refreshCount) i := 0 for qid, _ := range refresh { qids[i] = qid i++ } data["refresh"] = qids response, err := json.Marshal(data) if err != nil { logging.Error("ERROR encoding JSON: %s for: %v", err, data) return nil, http.StatusInternalServerError } return response, http.StatusOK } return nil, http.StatusNotFound }
func (frontend *Frontend) ServeHTTP(conn http.ResponseWriter, req *http.Request) { originalHost := req.Host // Redirect all requests to the "official" public host if the Host header // doesn't match. if !frontend.isValidHost(originalHost) { conn.Header().Set("Location", frontend.RedirectURL) conn.WriteHeader(http.StatusMovedPermanently) conn.Write(frontend.RedirectHTML) frontend.Log(HTTPS_REDIRECT, http.StatusMovedPermanently, originalHost, req) return } // Return the HTTP 503 error page if we're in maintenance mode. if frontend.MaintenanceMode { headers := conn.Header() headers.Set("Content-Type", "text/html; charset=utf-8") headers.Set("Content-Length", frontend.Error503Length) conn.WriteHeader(http.StatusServiceUnavailable) conn.Write(frontend.Error503) frontend.Log(HTTPS_MAINTENANCE, http.StatusServiceUnavailable, originalHost, req) return } reqPath := req.URL.Path // Handle requests for any files exposed within the static directory. if staticFile, ok := frontend.StaticFiles[reqPath]; ok { expires := time.SecondsToUTC(time.Seconds() + frontend.StaticMaxAge) headers := conn.Header() headers.Set("Expires", expires.Format(http.TimeFormat)) headers.Set("Cache-Control", frontend.StaticCache) headers.Set("Etag", staticFile.ETag) if req.Header.Get("If-None-Match") == staticFile.ETag { conn.WriteHeader(http.StatusNotModified) frontend.Log(HTTPS_STATIC, http.StatusNotModified, originalHost, req) return } // Special case /.well-known/oauth.json?callback= requests. if reqPath == "/.well-known/oauth.json" && req.URL.RawQuery != "" { query, err := http.ParseQuery(req.URL.RawQuery) if err != nil { logging.Error("Error parsing oauth.json query string %q: %s", req.URL.RawQuery, err) frontend.ServeError400(conn, originalHost, req) return } if callbackList, found := query["callback"]; found { callback := callbackList[0] if callback != "" { respLen := len(callback) + len(staticFile.Content) + 2 headers.Set("Content-Type", "text/javascript") headers.Set("Content-Length", fmt.Sprintf("%d", respLen)) conn.WriteHeader(http.StatusOK) conn.Write([]byte(callback)) conn.Write([]byte{'('}) conn.Write(staticFile.Content) conn.Write([]byte{')'}) frontend.Log(HTTPS_STATIC, http.StatusOK, originalHost, req) return } } } headers.Set("Content-Type", staticFile.Mimetype) headers.Set("Content-Length", staticFile.Size) conn.WriteHeader(http.StatusOK) conn.Write(staticFile.Content) frontend.Log(HTTPS_STATIC, http.StatusOK, originalHost, req) return } if frontend.LiveMode { // Handle WebSocket requests. if strings.HasPrefix(reqPath, frontend.WebsocketPrefix) { websocket.Handler(frontend.getWebSocketHandler()).ServeHTTP(conn, req) return } // Handle long-polling Comet requests. if strings.HasPrefix(reqPath, frontend.CometPrefix) { query, err := http.ParseQuery(req.URL.RawQuery) if err != nil { logging.Error("Error parsing Comet query string %q: %s", req.URL.RawQuery, err) frontend.ServeError400(conn, originalHost, req) return } queryReq, found := query["q"] if !found { frontend.ServeError400(conn, originalHost, req) return } response, status := getLiveItems(queryReq[0]) headers := conn.Header() headers.Set("Content-Type", "application/json") headers.Set("Content-Length", fmt.Sprintf("%d", len(response))) conn.WriteHeader(status) conn.Write(response) frontend.Log(HTTPS_COMET, status, originalHost, req) return } } // Open a connection to the upstream server. upstreamConn, err := net.Dial("tcp", frontend.upstreamAddr) if err != nil { logging.Error("Couldn't connect to upstream: %s", err) frontend.ServeError502(conn, originalHost, req) return } var clientIP string var upstream net.Conn splitPoint := strings.LastIndex(req.RemoteAddr, ":") if splitPoint == -1 { clientIP = req.RemoteAddr } else { clientIP = req.RemoteAddr[0:splitPoint] } if frontend.upstreamTLS { upstream = tls.Client(upstreamConn, tlsconf.Config) defer upstream.Close() } else { upstream = upstreamConn } // Modify the request Host: and User-Agent: headers. req.Host = frontend.upstreamHost req.Header.Set( "User-Agent", fmt.Sprintf("%s, %s, %s", req.UserAgent(), clientIP, originalHost)) // Send the request to the upstream server. err = req.Write(upstream) if err != nil { logging.Error("Error writing to the upstream server: %s", err) frontend.ServeError502(conn, originalHost, req) return } // Parse the response from upstream. resp, err := http.ReadResponse(bufio.NewReader(upstream), req) if err != nil { logging.Error("Error parsing response from upstream: %s", err) frontend.ServeError502(conn, originalHost, req) return } defer resp.Body.Close() // Get the original request header. headers := conn.Header() // Set a variable to hold the X-Live header value if present. var liveLength int if frontend.LiveMode { xLive := resp.Header.Get("X-Live") if xLive != "" { // If the X-Live header was set, parse it into an int. liveLength, err = strconv.Atoi(xLive) if err != nil { logging.Error("Error converting X-Live header value %q: %s", xLive, err) frontend.ServeError500(conn, originalHost, req) return } resp.Header.Del("X-Live") } } var body []byte if liveLength > 0 { var gzipSet bool var respBody io.ReadCloser // Check Content-Encoding to see if upstream sent gzipped content. if resp.Header.Get("Content-Encoding") == "gzip" { gzipSet = true respBody, err = gzip.NewReader(resp.Body) if err != nil { logging.Error("Error reading gzipped response from upstream: %s", err) frontend.ServeError500(conn, originalHost, req) return } defer respBody.Close() } else { respBody = resp.Body } // Read the X-Live content from the response body. liveMessage := make([]byte, liveLength) n, err := respBody.Read(liveMessage) if n != liveLength || err != nil { logging.Error("Error reading X-Live response from upstream: %s", err) frontend.ServeError500(conn, originalHost, req) return } // Read the response to send back to the original request. body, err = ioutil.ReadAll(respBody) if err != nil { logging.Error("Error reading non X-Live response from upstream: %s", err) frontend.ServeError500(conn, originalHost, req) return } // Re-encode the response if it had been gzipped by upstream. if gzipSet { buffer := &bytes.Buffer{} encoder, err := gzip.NewWriter(buffer) if err != nil { logging.Error("Error creating a new gzip Writer: %s", err) frontend.ServeError500(conn, originalHost, req) return } n, err = encoder.Write(body) if n != len(body) || err != nil { logging.Error("Error writing to the gzip Writer: %s", err) frontend.ServeError500(conn, originalHost, req) return } err = encoder.Close() if err != nil { logging.Error("Error finalising the write to the gzip Writer: %s", err) frontend.ServeError500(conn, originalHost, req) return } body = buffer.Bytes() } resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(body))) liveChannel <- liveMessage } else { // Read the full response body. body, err = ioutil.ReadAll(resp.Body) if err != nil { logging.Error("Error reading response from upstream: %s", err) frontend.ServeError502(conn, originalHost, req) return } } // Set the received headers back to the initial connection. for k, values := range resp.Header { for _, v := range values { headers.Add(k, v) } } // Write the response body back to the initial connection. conn.WriteHeader(resp.StatusCode) conn.Write(body) frontend.Log(HTTPS_UPSTREAM, resp.StatusCode, originalHost, req) }