func (m *mp4Handler) RequestHandle(ctx context.Context, w http.ResponseWriter, r *http.Request) { // Handle only GET requests with ContentLength of 0 without a Range header if r.Method != "GET" || len(r.Header.Get("Range")) > 0 || r.ContentLength > 0 { m.next.RequestHandle(ctx, w, r) return } // parse the request var start, err = strconv.Atoi(r.URL.Query().Get(startKey)) if err != nil || 0 >= start { // that start is not ok m.next.RequestHandle(ctx, w, r) return } var startTime = time.Duration(start) * time.Second var newreq = copyRequest(r) removeQueryArgument(newreq.URL, startKey) var header = make(http.Header) var rr = &rangeReader{ ctx: ctx, req: copyRequest(newreq), location: m.loc, next: m.next, callback: func(frw *httputils.FlexibleResponseWriter) bool { if len(header) == 0 { httputils.CopyHeadersWithout(frw.Header(), header, skipHeaders...) } else { return frw.Header().Get("Last-Modified") == header.Get("Last-Modified") } return true }, } var video *mp4.MP4 video, err = mp4.Decode(rr) if err != nil { m.loc.Logger.Errorf("error from the mp4.Decode - %s", err) m.next.RequestHandle(ctx, w, r) return } if video == nil || video.Moov == nil { // missing something? m.next.RequestHandle(ctx, w, r) return } cl, err := clip.New(video, startTime, rr) if err != nil { m.loc.Logger.Errorf("error while clipping a video(%+v) - %s", video, err) m.next.RequestHandle(ctx, w, r) return } httputils.CopyHeaders(header, w.Header()) w.Header().Set("Content-Length", strconv.FormatUint(cl.Size(), 10)) size, err := cl.WriteTo(w) m.loc.Logger.Debugf("wrote %d", size) if err != nil { m.loc.Logger.Logf("error on writing the clip response - %s", err) } if uint64(size) != cl.Size() { m.loc.Logger.Debugf("handler.mp4[%p]: expected to write %d but wrote %d", m, cl.Size(), size) } }
// Returns a new HTTP 1.1 request that has no body. It also clears headers like // accept-encoding and rearranges the requested ranges so they match part func (h *reqHandler) getNormalizedRequest() *http.Request { url := *h.req.URL result := &http.Request{ Method: h.req.Method, URL: &url, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: make(http.Header), Host: h.req.Host, } httputils.CopyHeadersWithout(h.req.Header, result.Header, "Accept-Encoding") //!TODO: fix requested range to be divisible by the storage partSize return result }
func (p *ReverseProxy) getOutRequest(rw http.ResponseWriter, req *http.Request, upstream types.Upstream) (*http.Request, error) { outreq := new(http.Request) *outreq = *req url := *req.URL outreq.URL = &url outreq.Header = http.Header{} httputils.CopyHeadersWithout(req.Header, outreq.Header, hopHeaders...) outreq.Header.Set("User-Agent", p.Settings.UserAgent) // If we don't set it, Go sets it for us to something stupid... outreq.Proto = "HTTP/1.1" outreq.ProtoMajor = 1 outreq.ProtoMinor = 1 outreq.Close = false upAddr, err := upstream.GetAddress(p.Settings.UpstreamHashPrefix + req.URL.RequestURI()) if err != nil { return nil, fmt.Errorf("[%p] Proxy handler could not get an upstream address: %v", req, err) } p.Logger.Debugf("[%p] Using upstream %s (%s) to proxy request", req, upAddr, upAddr.OriginalURL) outreq.URL.Scheme = upAddr.Scheme outreq.URL.Host = upAddr.Host // Set the correct host if p.Settings.HostHeader != "" { outreq.Host = p.Settings.HostHeader } else if p.Settings.HostHeaderKeepOriginal { if req.Host != "" { outreq.Host = req.Host } else { outreq.Host = req.URL.Host } } else { outreq.Host = upAddr.OriginalURL.Host } if closeNotifier, ok := rw.(http.CloseNotifier); ok { reqDone := make(chan struct{}) defer close(reqDone) clientGone := closeNotifier.CloseNotify() outreq.Body = struct { io.Reader io.Closer }{ Reader: &runOnFirstRead{ Reader: outreq.Body, fn: func() { go func() { select { case <-clientGone: upstream.CancelRequest(outreq) case <-reqDone: } }() }, }, Closer: outreq.Body, } } if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { // If we aren't the first proxy retain prior // X-Forwarded-For information as a comma+space // separated list and fold multiple headers into one. if prior, ok := outreq.Header["X-Forwarded-For"]; ok { clientIP = strings.Join(prior, ", ") + ", " + clientIP } outreq.Header.Set("X-Forwarded-For", clientIP) } return outreq, nil }
func (h *reqHandler) getResponseHook() func(*httputils.FlexibleResponseWriter) { return func(rw *httputils.FlexibleResponseWriter) { h.Logger.Debugf("[%p] Received headers for %s, sending them to client...", h.req, h.req.URL) httputils.CopyHeadersWithout(rw.Headers, h.resp.Header(), hopHeaders...) h.resp.WriteHeader(rw.Code) isCacheable := cacheutils.IsResponseCacheable(rw.Code, rw.Headers) if !isCacheable { h.Logger.Debugf("[%p] Response is non-cacheable", h.req) rw.BodyWriter = utils.AddCloser(h.resp) return } expiresIn := cacheutils.ResponseExpiresIn(rw.Headers, h.CacheDefaultDuration) if expiresIn <= 0 { h.Logger.Debugf("[%p] Response expires in the past: %s", h.req, expiresIn) rw.BodyWriter = utils.AddCloser(h.resp) return } responseRange, err := httputils.GetResponseRange(rw.Code, rw.Headers) if err != nil { h.Logger.Debugf("[%p] Was not able to get response range (%s)", h.req, err) rw.BodyWriter = utils.AddCloser(h.resp) return } h.Logger.Debugf("[%p] Response is cacheable! Caching metadata and parts...", h.req) code := rw.Code if code == http.StatusPartialContent { // 206 is returned only if the server would have returned 200 with a normal request code = http.StatusOK } //!TODO: maybe call cached time.Now. See the comment in utils.IsMetadataFresh now := time.Now() obj := &types.ObjectMetadata{ ID: h.objID, ResponseTimestamp: now.Unix(), Code: code, Size: responseRange.ObjSize, Headers: make(http.Header), ExpiresAt: now.Add(expiresIn).Unix(), } httputils.CopyHeadersWithout(rw.Headers, obj.Headers, metadataHeadersToFilter...) if obj.Headers.Get("Date") == "" { // maybe the server does not return date, we should set it then obj.Headers.Set("Date", now.Format(http.TimeFormat)) } //!TODO: consult the cache algorithm whether to save the metadata //!TODO: optimize this, save the metadata only when it's newer //!TODO: also, error if we already have fresh metadata but the received metadata is different if err := h.Cache.Storage.SaveMetadata(obj); err != nil { h.Logger.Errorf("[%p] Could not save metadata for %s: %s", h.req, obj.ID, err) rw.BodyWriter = utils.AddCloser(h.resp) return } if h.req.Method == "HEAD" { rw.BodyWriter = utils.AddCloser(h.resp) return } rw.BodyWriter = utils.MultiWriteCloser( utils.AddCloser(h.resp), PartWriter(h.Cache, h.objID, *responseRange), ) h.Logger.Debugf("[%p] Setting the cached data to expire in %s", h.req, expiresIn) h.Cache.Scheduler.AddEvent( h.objID.Hash(), storage.GetExpirationHandler(h.Cache, h.Logger, h.objID), expiresIn, ) } }