func (rp *FastReverseProxy) serveWebsocket(dstHost string, reqData *RequestData, ctx *fasthttp.RequestCtx) { req := &ctx.Request uri := req.URI() uri.SetHost(dstHost) dstConn, err := rp.dialFunc(dstHost) if err != nil { log.LogError(reqData.String(), string(uri.Path()), err) return } if clientIP, _, err := net.SplitHostPort(ctx.RemoteAddr().String()); err == nil { if prior := string(req.Header.Peek("X-Forwarded-For")); len(prior) > 0 { clientIP = prior + ", " + clientIP } req.Header.Set("X-Forwarded-For", clientIP) } _, err = req.WriteTo(dstConn) if err != nil { log.LogError(reqData.String(), string(uri.Path()), err) return } ctx.Hijack(func(conn net.Conn) { defer dstConn.Close() defer conn.Close() errc := make(chan error, 2) cp := func(dst io.Writer, src io.Reader) { _, err := io.Copy(dst, src) errc <- err } go cp(dstConn, conn) go cp(conn, dstConn) <-errc }) }
// /raw/msgs/:topic/:ver func (this *Gateway) pubRawHandler(ctx *fasthttp.RequestCtx, params fasthttprouter.Params) { var ( topic string ver string appid string pubkey string ) ver = params.ByName(UrlParamVersion) topic = params.ByName(UrlParamTopic) header := ctx.Request.Header appid = string(header.Peek(HttpHeaderAppid)) pubkey = string(header.Peek(HttpHeaderPubkey)) if err := manager.Default.OwnTopic(appid, pubkey, topic); err != nil { log.Error("app[%s] %s %+v: %v", appid, ctx.RemoteAddr(), params, err) ctx.SetConnectionClose() ctx.Error("invalid secret", fasthttp.StatusUnauthorized) return } cluster, found := manager.Default.LookupCluster(appid) if !found { log.Error("cluster not found for app: %s", appid) ctx.Error("invalid appid", fasthttp.StatusBadRequest) return } var out = map[string]string{ "store": "kafka", "broker.list": strings.Join(meta.Default.BrokerList(cluster), ","), "topic": manager.Default.KafkaTopic(appid, topic, ver), } b, _ := json.Marshal(out) ctx.SetContentType("application/json; charset=utf8") ctx.Write(b) }
func (rp *FastReverseProxy) handler(ctx *fasthttp.RequestCtx) { req := &ctx.Request resp := &ctx.Response host := string(req.Header.Host()) uri := req.URI() if host == "__ping__" && len(uri.Path()) == 1 && uri.Path()[0] == byte('/') { resp.SetBody(okResponse) return } reqData, err := rp.Router.ChooseBackend(host) if err != nil { log.LogError(reqData.String(), string(uri.Path()), err) } dstScheme := "" dstHost := "" u, err := url.Parse(reqData.Backend) if err == nil { dstScheme = u.Scheme dstHost = u.Host } else { log.LogError(reqData.String(), string(uri.Path()), err) } if dstHost == "" { dstHost = reqData.Backend } upgrade := req.Header.Peek("Upgrade") if len(upgrade) > 0 && bytes.Compare(bytes.ToLower(upgrade), websocketUpgrade) == 0 { resp.SkipResponse = true rp.serveWebsocket(dstHost, reqData, ctx) return } var backendDuration time.Duration logEntry := func() *log.LogEntry { proto := "HTTP/1.0" if req.Header.IsHTTP11() { proto = "HTTP/1.1" } return &log.LogEntry{ Now: time.Now(), BackendDuration: backendDuration, TotalDuration: time.Since(reqData.StartTime), BackendKey: reqData.BackendKey, RemoteAddr: ctx.RemoteAddr().String(), Method: string(ctx.Method()), Path: string(uri.Path()), Proto: proto, Referer: string(ctx.Referer()), UserAgent: string(ctx.UserAgent()), RequestIDHeader: rp.RequestIDHeader, RequestID: string(req.Header.Peek(rp.RequestIDHeader)), StatusCode: resp.StatusCode(), ContentLength: int64(resp.Header.ContentLength()), } } isDebug := len(req.Header.Peek("X-Debug-Router")) > 0 req.Header.Del("X-Debug-Router") if dstHost == "" { resp.SetStatusCode(http.StatusBadRequest) resp.SetBody(noRouteResponseContent) rp.debugHeaders(resp, reqData, isDebug) endErr := rp.Router.EndRequest(reqData, false, logEntry) if endErr != nil { log.LogError(reqData.String(), string(uri.Path()), endErr) } return } if rp.RequestIDHeader != "" && len(req.Header.Peek(rp.RequestIDHeader)) == 0 { unparsedID, err := uuid.NewV4() if err == nil { req.Header.Set(rp.RequestIDHeader, unparsedID.String()) } else { log.LogError(reqData.String(), string(uri.Path()), fmt.Errorf("unable to generate request id: %s", err)) } } hostOnly, _, _ := net.SplitHostPort(dstHost) if hostOnly == "" { hostOnly = dstHost } isIP := net.ParseIP(hostOnly) != nil if !isIP { req.Header.SetBytesV("X-Host", uri.Host()) req.Header.SetBytesV("X-Forwarded-Host", uri.Host()) uri.SetHost(hostOnly) } client := rp.getClient(dstHost, dstScheme == "https") t0 := time.Now().UTC() err = client.Do(req, resp) backendDuration = time.Since(t0) markAsDead := false if err != nil { var isTimeout bool if netErr, ok := err.(net.Error); ok { markAsDead = !netErr.Temporary() isTimeout = netErr.Timeout() } if isTimeout { markAsDead = false err = fmt.Errorf("request timed out after %v: %s", time.Since(reqData.StartTime), err) } else { err = fmt.Errorf("error in backend request: %s", err) } if markAsDead { err = fmt.Errorf("%s *DEAD*", err) } resp.SetStatusCode(http.StatusServiceUnavailable) log.LogError(reqData.String(), string(uri.Path()), err) } rp.debugHeaders(resp, reqData, isDebug) endErr := rp.Router.EndRequest(reqData, markAsDead, logEntry) if endErr != nil { log.LogError(reqData.String(), string(uri.Path()), endErr) } }
// /topics/:topic/:ver?key=mykey&async=1&delay=100 func (this *Gateway) pubHandler(ctx *fasthttp.RequestCtx, params fasthttprouter.Params) { t1 := time.Now() topic := params.ByName(UrlParamTopic) header := ctx.Request.Header appid := string(header.Peek(HttpHeaderAppid)) pubkey := string(header.Peek(HttpHeaderPubkey)) if err := manager.Default.OwnTopic(appid, pubkey, topic); err != nil { log.Error("app[%s] %s %+v: %v", appid, ctx.RemoteAddr(), params, err) ctx.SetConnectionClose() ctx.Error("invalid secret", fasthttp.StatusUnauthorized) return } msgLen := ctx.Request.Header.ContentLength() switch { case msgLen == -1: log.Warn("pub[%s] %s %+v invalid content length", appid, ctx.RemoteAddr(), params) ctx.Error("invalid content length", fasthttp.StatusBadRequest) return case int64(msgLen) > options.MaxPubSize: log.Warn("pub[%s] %s %+v too big content length:%d", appid, ctx.RemoteAddr(), params, msgLen) ctx.Error(ErrTooBigPubMessage.Error(), fasthttp.StatusBadRequest) return case msgLen < options.MinPubSize: log.Warn("pub[%s] %s %+v too small content length:%d", appid, ctx.RemoteAddr(), params, msgLen) ctx.Error(ErrTooSmallPubMessage.Error(), fasthttp.StatusBadRequest) return } ver := params.ByName(UrlParamVersion) queryArgs := ctx.Request.URI().QueryArgs() key := queryArgs.Peek("key") asyncArg := queryArgs.Peek("async") async := len(asyncArg) == 1 && asyncArg[0] == '1' //delay := hack.String(queryArgs.Peek("delay")) if options.Debug { log.Debug("pub[%s] %s {topic:%s, ver:%s, key:%s, async:%+v} %s", appid, ctx.RemoteAddr(), topic, ver, key, async, string(ctx.Request.Body())) } if !options.DisableMetrics { this.pubMetrics.PubQps.Mark(1) this.pubMetrics.PubMsgSize.Update(int64(len(ctx.PostBody()))) } pubMethod := store.DefaultPubStore.SyncPub if async { pubMethod = store.DefaultPubStore.AsyncPub } cluster, found := manager.Default.LookupCluster(appid) if !found { log.Error("cluster not found for app: %s", appid) ctx.Error("invalid appid", fasthttp.StatusBadRequest) return } err := pubMethod(cluster, manager.Default.KafkaTopic(appid, topic, ver), key, ctx.PostBody()) if err != nil { if !options.DisableMetrics { this.pubMetrics.PubFail(appid, topic, ver) } log.Error("%s: %v", ctx.RemoteAddr(), err) ctx.Error(err.Error(), fasthttp.StatusInternalServerError) return } // write the reponse ctx.Write(ResponseOk) if !options.DisableMetrics { this.pubMetrics.PubOk(appid, topic, ver) this.pubMetrics.PubLatency.Update(time.Since(t1).Nanoseconds() / 1e6) // in ms } }