Ejemplo n.º 1
0
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
}
Ejemplo n.º 3
0
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,
		)
	}
}