Merge pull request #36164 from emil2k/gzip-archive

Content encoding negotiation added to archive request.
This commit is contained in:
Yong Tang 2018-02-24 14:06:06 -08:00 committed by GitHub
commit 20c4efee7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1006 additions and 3 deletions

View file

@ -1,6 +1,8 @@
package container // import "github.com/docker/docker/api/server/router/container"
import (
"compress/flate"
"compress/gzip"
"encoding/base64"
"encoding/json"
"io"
@ -9,6 +11,7 @@ import (
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
gddohttputil "github.com/golang/gddo/httputil"
"golang.org/x/net/context"
)
@ -81,6 +84,29 @@ func (s *containerRouter) headContainersArchive(ctx context.Context, w http.Resp
return setContainerPathStatHeader(stat, w.Header())
}
func writeCompressedResponse(w http.ResponseWriter, r *http.Request, body io.Reader) error {
var cw io.Writer
switch gddohttputil.NegotiateContentEncoding(r, []string{"gzip", "deflate"}) {
case "gzip":
gw := gzip.NewWriter(w)
defer gw.Close()
cw = gw
w.Header().Set("Content-Encoding", "gzip")
case "deflate":
fw, err := flate.NewWriter(w, flate.DefaultCompression)
if err != nil {
return err
}
defer fw.Close()
cw = fw
w.Header().Set("Content-Encoding", "deflate")
default:
cw = w
}
_, err := io.Copy(cw, body)
return err
}
func (s *containerRouter) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
v, err := httputils.ArchiveFormValues(r, vars)
if err != nil {
@ -98,9 +124,7 @@ func (s *containerRouter) getContainersArchive(ctx context.Context, w http.Respo
}
w.Header().Set("Content-Type", "application/x-tar")
_, err = io.Copy(w, tarArchive)
return err
return writeCompressedResponse(w, r, tarArchive)
}
func (s *containerRouter) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

View file

@ -5,6 +5,7 @@ github.com/Microsoft/go-winio v0.4.6
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a
github.com/gorilla/context v1.1
github.com/gorilla/mux v1.1
github.com/Microsoft/opengcs v0.3.6

27
vendor/github.com/golang/gddo/LICENSE generated vendored Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2013 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

44
vendor/github.com/golang/gddo/README.markdown generated vendored Normal file
View file

@ -0,0 +1,44 @@
This project is the source for http://godoc.org/
[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](http://godoc.org/github.com/golang/gddo)
[![Build
Status](https://travis-ci.org/golang/gddo.svg?branch=master)](https://travis-ci.org/golang/gddo)
The code in this project is designed to be used by godoc.org. Send mail to
golang-dev@googlegroups.com if you want to discuss other uses of the code.
## Feedback
Send ideas and questions to golang-dev@googlegroups.com. Request features and
report bugs using the [GitHub Issue
Tracker](https://github.com/golang/gddo/issues/new).
## Contributions
Contributions to this project are welcome, though please [file an
issue](https://github.com/golang/gddo/issues/new). before starting work on
anything major.
**We do not accept GitHub pull requests**
Please refer to the [Contribution
Guidelines](https://golang.org/doc/contribute.html) on how to submit changes.
We use https://go-review.googlesource.com to review change submissions.
## Getting the Source
To get started contributing to this project, clone the repository from its
canonical location
```
git clone https://go.googlesource.com/gddo $GOPATH/src/github.com/golang/gddo
```
Information on how to set up a local environment is available at
https://github.com/golang/gddo/wiki/Development-Environment-Setup.
## More Documentation
More documentation about this project is available on the
[wiki](https://github.com/golang/gddo/wiki).

95
vendor/github.com/golang/gddo/httputil/buster.go generated vendored Normal file
View file

@ -0,0 +1,95 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package httputil
import (
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
)
type busterWriter struct {
headerMap http.Header
status int
io.Writer
}
func (bw *busterWriter) Header() http.Header {
return bw.headerMap
}
func (bw *busterWriter) WriteHeader(status int) {
bw.status = status
}
// CacheBusters maintains a cache of cache busting tokens for static resources served by Handler.
type CacheBusters struct {
Handler http.Handler
mu sync.Mutex
tokens map[string]string
}
func sanitizeTokenRune(r rune) rune {
if r <= ' ' || r >= 127 {
return -1
}
// Convert percent encoding reserved characters to '-'.
if strings.ContainsRune("!#$&'()*+,/:;=?@[]", r) {
return '-'
}
return r
}
// Get returns the cache busting token for path. If the token is not already
// cached, Get issues a HEAD request on handler and uses the response ETag and
// Last-Modified headers to compute a token.
func (cb *CacheBusters) Get(path string) string {
cb.mu.Lock()
if cb.tokens == nil {
cb.tokens = make(map[string]string)
}
token, ok := cb.tokens[path]
cb.mu.Unlock()
if ok {
return token
}
w := busterWriter{
Writer: ioutil.Discard,
headerMap: make(http.Header),
}
r := &http.Request{URL: &url.URL{Path: path}, Method: "HEAD"}
cb.Handler.ServeHTTP(&w, r)
if w.status == 200 {
token = w.headerMap.Get("Etag")
if token == "" {
token = w.headerMap.Get("Last-Modified")
}
token = strings.Trim(token, `" `)
token = strings.Map(sanitizeTokenRune, token)
}
cb.mu.Lock()
cb.tokens[path] = token
cb.mu.Unlock()
return token
}
// AppendQueryParam appends the token as a query parameter to path.
func (cb *CacheBusters) AppendQueryParam(path string, name string) string {
token := cb.Get(path)
if token == "" {
return path
}
return path + "?" + name + "=" + token
}

298
vendor/github.com/golang/gddo/httputil/header/header.go generated vendored Normal file
View file

@ -0,0 +1,298 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// Package header provides functions for parsing HTTP headers.
package header
import (
"net/http"
"strings"
"time"
)
// Octet types from RFC 2616.
var octetTypes [256]octetType
type octetType byte
const (
isToken octetType = 1 << iota
isSpace
)
func init() {
// OCTET = <any 8-bit sequence of data>
// CHAR = <any US-ASCII character (octets 0 - 127)>
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
// CR = <US-ASCII CR, carriage return (13)>
// LF = <US-ASCII LF, linefeed (10)>
// SP = <US-ASCII SP, space (32)>
// HT = <US-ASCII HT, horizontal-tab (9)>
// <"> = <US-ASCII double-quote mark (34)>
// CRLF = CR LF
// LWS = [CRLF] 1*( SP | HT )
// TEXT = <any OCTET except CTLs, but including LWS>
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
// token = 1*<any CHAR except CTLs or separators>
// qdtext = <any TEXT except <">>
for c := 0; c < 256; c++ {
var t octetType
isCtl := c <= 31 || c == 127
isChar := 0 <= c && c <= 127
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
t |= isSpace
}
if isChar && !isCtl && !isSeparator {
t |= isToken
}
octetTypes[c] = t
}
}
// Copy returns a shallow copy of the header.
func Copy(header http.Header) http.Header {
h := make(http.Header)
for k, vs := range header {
h[k] = vs
}
return h
}
var timeLayouts = []string{"Mon, 02 Jan 2006 15:04:05 GMT", time.RFC850, time.ANSIC}
// ParseTime parses the header as time. The zero value is returned if the
// header is not present or there is an error parsing the
// header.
func ParseTime(header http.Header, key string) time.Time {
if s := header.Get(key); s != "" {
for _, layout := range timeLayouts {
if t, err := time.Parse(layout, s); err == nil {
return t.UTC()
}
}
}
return time.Time{}
}
// ParseList parses a comma separated list of values. Commas are ignored in
// quoted strings. Quoted values are not unescaped or unquoted. Whitespace is
// trimmed.
func ParseList(header http.Header, key string) []string {
var result []string
for _, s := range header[http.CanonicalHeaderKey(key)] {
begin := 0
end := 0
escape := false
quote := false
for i := 0; i < len(s); i++ {
b := s[i]
switch {
case escape:
escape = false
end = i + 1
case quote:
switch b {
case '\\':
escape = true
case '"':
quote = false
}
end = i + 1
case b == '"':
quote = true
end = i + 1
case octetTypes[b]&isSpace != 0:
if begin == end {
begin = i + 1
end = begin
}
case b == ',':
if begin < end {
result = append(result, s[begin:end])
}
begin = i + 1
end = begin
default:
end = i + 1
}
}
if begin < end {
result = append(result, s[begin:end])
}
}
return result
}
// ParseValueAndParams parses a comma separated list of values with optional
// semicolon separated name-value pairs. Content-Type and Content-Disposition
// headers are in this format.
func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) {
params = make(map[string]string)
s := header.Get(key)
value, s = expectTokenSlash(s)
if value == "" {
return
}
value = strings.ToLower(value)
s = skipSpace(s)
for strings.HasPrefix(s, ";") {
var pkey string
pkey, s = expectToken(skipSpace(s[1:]))
if pkey == "" {
return
}
if !strings.HasPrefix(s, "=") {
return
}
var pvalue string
pvalue, s = expectTokenOrQuoted(s[1:])
if pvalue == "" {
return
}
pkey = strings.ToLower(pkey)
params[pkey] = pvalue
s = skipSpace(s)
}
return
}
// AcceptSpec describes an Accept* header.
type AcceptSpec struct {
Value string
Q float64
}
// ParseAccept parses Accept* headers.
func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
loop:
for _, s := range header[key] {
for {
var spec AcceptSpec
spec.Value, s = expectTokenSlash(s)
if spec.Value == "" {
continue loop
}
spec.Q = 1.0
s = skipSpace(s)
if strings.HasPrefix(s, ";") {
s = skipSpace(s[1:])
if !strings.HasPrefix(s, "q=") {
continue loop
}
spec.Q, s = expectQuality(s[2:])
if spec.Q < 0.0 {
continue loop
}
}
specs = append(specs, spec)
s = skipSpace(s)
if !strings.HasPrefix(s, ",") {
continue loop
}
s = skipSpace(s[1:])
}
}
return
}
func skipSpace(s string) (rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isSpace == 0 {
break
}
}
return s[i:]
}
func expectToken(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isToken == 0 {
break
}
}
return s[:i], s[i:]
}
func expectTokenSlash(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
b := s[i]
if (octetTypes[b]&isToken == 0) && b != '/' {
break
}
}
return s[:i], s[i:]
}
func expectQuality(s string) (q float64, rest string) {
switch {
case len(s) == 0:
return -1, ""
case s[0] == '0':
q = 0
case s[0] == '1':
q = 1
default:
return -1, ""
}
s = s[1:]
if !strings.HasPrefix(s, ".") {
return q, s
}
s = s[1:]
i := 0
n := 0
d := 1
for ; i < len(s); i++ {
b := s[i]
if b < '0' || b > '9' {
break
}
n = n*10 + int(b) - '0'
d *= 10
}
return q + float64(n)/float64(d), s[i:]
}
func expectTokenOrQuoted(s string) (value string, rest string) {
if !strings.HasPrefix(s, "\"") {
return expectToken(s)
}
s = s[1:]
for i := 0; i < len(s); i++ {
switch s[i] {
case '"':
return s[:i], s[i+1:]
case '\\':
p := make([]byte, len(s)-1)
j := copy(p, s[:i])
escape := true
for i = i + 1; i < len(s); i++ {
b := s[i]
switch {
case escape:
escape = false
p[j] = b
j++
case b == '\\':
escape = true
case b == '"':
return string(p[:j]), s[i+1:]
default:
p[j] = b
j++
}
}
return "", ""
}
}
return "", ""
}

25
vendor/github.com/golang/gddo/httputil/httputil.go generated vendored Normal file
View file

@ -0,0 +1,25 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// Package httputil is a toolkit for the Go net/http package.
package httputil
import (
"net"
"net/http"
)
// StripPort removes the port specification from an address.
func StripPort(s string) string {
if h, _, err := net.SplitHostPort(s); err == nil {
s = h
}
return s
}
// Error defines a type for a function that accepts a ResponseWriter for
// a Request with the HTTP status code and error.
type Error func(w http.ResponseWriter, r *http.Request, status int, err error)

79
vendor/github.com/golang/gddo/httputil/negotiate.go generated vendored Normal file
View file

@ -0,0 +1,79 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package httputil
import (
"github.com/golang/gddo/httputil/header"
"net/http"
"strings"
)
// NegotiateContentEncoding returns the best offered content encoding for the
// request's Accept-Encoding header. If two offers match with equal weight and
// then the offer earlier in the list is preferred. If no offers are
// acceptable, then "" is returned.
func NegotiateContentEncoding(r *http.Request, offers []string) string {
bestOffer := "identity"
bestQ := -1.0
specs := header.ParseAccept(r.Header, "Accept-Encoding")
for _, offer := range offers {
for _, spec := range specs {
if spec.Q > bestQ &&
(spec.Value == "*" || spec.Value == offer) {
bestQ = spec.Q
bestOffer = offer
}
}
}
if bestQ == 0 {
bestOffer = ""
}
return bestOffer
}
// NegotiateContentType returns the best offered content type for the request's
// Accept header. If two offers match with equal weight, then the more specific
// offer is preferred. For example, text/* trumps */*. If two offers match
// with equal weight and specificity, then the offer earlier in the list is
// preferred. If no offers match, then defaultOffer is returned.
func NegotiateContentType(r *http.Request, offers []string, defaultOffer string) string {
bestOffer := defaultOffer
bestQ := -1.0
bestWild := 3
specs := header.ParseAccept(r.Header, "Accept")
for _, offer := range offers {
for _, spec := range specs {
switch {
case spec.Q == 0.0:
// ignore
case spec.Q < bestQ:
// better match found
case spec.Value == "*/*":
if spec.Q > bestQ || bestWild > 2 {
bestQ = spec.Q
bestWild = 2
bestOffer = offer
}
case strings.HasSuffix(spec.Value, "/*"):
if strings.HasPrefix(offer, spec.Value[:len(spec.Value)-1]) &&
(spec.Q > bestQ || bestWild > 1) {
bestQ = spec.Q
bestWild = 1
bestOffer = offer
}
default:
if spec.Value == offer &&
(spec.Q > bestQ || bestWild > 0) {
bestQ = spec.Q
bestWild = 0
bestOffer = offer
}
}
}
}
return bestOffer
}

58
vendor/github.com/golang/gddo/httputil/respbuf.go generated vendored Normal file
View file

@ -0,0 +1,58 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package httputil
import (
"bytes"
"net/http"
"strconv"
)
// ResponseBuffer is the current response being composed by its owner.
// It implements http.ResponseWriter and io.WriterTo.
type ResponseBuffer struct {
buf bytes.Buffer
status int
header http.Header
}
// Write implements the http.ResponseWriter interface.
func (rb *ResponseBuffer) Write(p []byte) (int, error) {
return rb.buf.Write(p)
}
// WriteHeader implements the http.ResponseWriter interface.
func (rb *ResponseBuffer) WriteHeader(status int) {
rb.status = status
}
// Header implements the http.ResponseWriter interface.
func (rb *ResponseBuffer) Header() http.Header {
if rb.header == nil {
rb.header = make(http.Header)
}
return rb.header
}
// WriteTo implements the io.WriterTo interface.
func (rb *ResponseBuffer) WriteTo(w http.ResponseWriter) error {
for k, v := range rb.header {
w.Header()[k] = v
}
if rb.buf.Len() > 0 {
w.Header().Set("Content-Length", strconv.Itoa(rb.buf.Len()))
}
if rb.status != 0 {
w.WriteHeader(rb.status)
}
if rb.buf.Len() > 0 {
if _, err := w.Write(rb.buf.Bytes()); err != nil {
return err
}
}
return nil
}

265
vendor/github.com/golang/gddo/httputil/static.go generated vendored Normal file
View file

@ -0,0 +1,265 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package httputil
import (
"bytes"
"crypto/sha1"
"errors"
"fmt"
"github.com/golang/gddo/httputil/header"
"io"
"io/ioutil"
"mime"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
// StaticServer serves static files.
type StaticServer struct {
// Dir specifies the location of the directory containing the files to serve.
Dir string
// MaxAge specifies the maximum age for the cache control and expiration
// headers.
MaxAge time.Duration
// Error specifies the function used to generate error responses. If Error
// is nil, then http.Error is used to generate error responses.
Error Error
// MIMETypes is a map from file extensions to MIME types.
MIMETypes map[string]string
mu sync.Mutex
etags map[string]string
}
func (ss *StaticServer) resolve(fname string) string {
if path.IsAbs(fname) {
panic("Absolute path not allowed when creating a StaticServer handler")
}
dir := ss.Dir
if dir == "" {
dir = "."
}
fname = filepath.FromSlash(fname)
return filepath.Join(dir, fname)
}
func (ss *StaticServer) mimeType(fname string) string {
ext := path.Ext(fname)
var mimeType string
if ss.MIMETypes != nil {
mimeType = ss.MIMETypes[ext]
}
if mimeType == "" {
mimeType = mime.TypeByExtension(ext)
}
if mimeType == "" {
mimeType = "application/octet-stream"
}
return mimeType
}
func (ss *StaticServer) openFile(fname string) (io.ReadCloser, int64, string, error) {
f, err := os.Open(fname)
if err != nil {
return nil, 0, "", err
}
fi, err := f.Stat()
if err != nil {
f.Close()
return nil, 0, "", err
}
const modeType = os.ModeDir | os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
if fi.Mode()&modeType != 0 {
f.Close()
return nil, 0, "", errors.New("not a regular file")
}
return f, fi.Size(), ss.mimeType(fname), nil
}
// FileHandler returns a handler that serves a single file. The file is
// specified by a slash separated path relative to the static server's Dir
// field.
func (ss *StaticServer) FileHandler(fileName string) http.Handler {
id := fileName
fileName = ss.resolve(fileName)
return &staticHandler{
ss: ss,
id: func(_ string) string { return id },
open: func(_ string) (io.ReadCloser, int64, string, error) { return ss.openFile(fileName) },
}
}
// DirectoryHandler returns a handler that serves files from a directory tree.
// The directory is specified by a slash separated path relative to the static
// server's Dir field.
func (ss *StaticServer) DirectoryHandler(prefix, dirName string) http.Handler {
if !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
idBase := dirName
dirName = ss.resolve(dirName)
return &staticHandler{
ss: ss,
id: func(p string) string {
if !strings.HasPrefix(p, prefix) {
return "."
}
return path.Join(idBase, p[len(prefix):])
},
open: func(p string) (io.ReadCloser, int64, string, error) {
if !strings.HasPrefix(p, prefix) {
return nil, 0, "", errors.New("request url does not match directory prefix")
}
p = p[len(prefix):]
return ss.openFile(filepath.Join(dirName, filepath.FromSlash(p)))
},
}
}
// FilesHandler returns a handler that serves the concatentation of the
// specified files. The files are specified by slash separated paths relative
// to the static server's Dir field.
func (ss *StaticServer) FilesHandler(fileNames ...string) http.Handler {
// todo: cache concatenated files on disk and serve from there.
mimeType := ss.mimeType(fileNames[0])
var buf []byte
var openErr error
for _, fileName := range fileNames {
p, err := ioutil.ReadFile(ss.resolve(fileName))
if err != nil {
openErr = err
buf = nil
break
}
buf = append(buf, p...)
}
id := strings.Join(fileNames, " ")
return &staticHandler{
ss: ss,
id: func(_ string) string { return id },
open: func(p string) (io.ReadCloser, int64, string, error) {
return ioutil.NopCloser(bytes.NewReader(buf)), int64(len(buf)), mimeType, openErr
},
}
}
type staticHandler struct {
id func(fname string) string
open func(p string) (io.ReadCloser, int64, string, error)
ss *StaticServer
}
func (h *staticHandler) error(w http.ResponseWriter, r *http.Request, status int, err error) {
http.Error(w, http.StatusText(status), status)
}
func (h *staticHandler) etag(p string) (string, error) {
id := h.id(p)
h.ss.mu.Lock()
if h.ss.etags == nil {
h.ss.etags = make(map[string]string)
}
etag := h.ss.etags[id]
h.ss.mu.Unlock()
if etag != "" {
return etag, nil
}
// todo: if a concurrent goroutine is calculating the hash, then wait for
// it instead of computing it again here.
rc, _, _, err := h.open(p)
if err != nil {
return "", err
}
defer rc.Close()
w := sha1.New()
_, err = io.Copy(w, rc)
if err != nil {
return "", err
}
etag = fmt.Sprintf(`"%x"`, w.Sum(nil))
h.ss.mu.Lock()
h.ss.etags[id] = etag
h.ss.mu.Unlock()
return etag, nil
}
func (h *staticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
p := path.Clean(r.URL.Path)
if p != r.URL.Path {
http.Redirect(w, r, p, 301)
return
}
etag, err := h.etag(p)
if err != nil {
h.error(w, r, http.StatusNotFound, err)
return
}
maxAge := h.ss.MaxAge
if maxAge == 0 {
maxAge = 24 * time.Hour
}
if r.FormValue("v") != "" {
maxAge = 365 * 24 * time.Hour
}
cacheControl := fmt.Sprintf("public, max-age=%d", maxAge/time.Second)
for _, e := range header.ParseList(r.Header, "If-None-Match") {
if e == etag {
w.Header().Set("Cache-Control", cacheControl)
w.Header().Set("Etag", etag)
w.WriteHeader(http.StatusNotModified)
return
}
}
rc, cl, ct, err := h.open(p)
if err != nil {
h.error(w, r, http.StatusNotFound, err)
return
}
defer rc.Close()
w.Header().Set("Cache-Control", cacheControl)
w.Header().Set("Etag", etag)
if ct != "" {
w.Header().Set("Content-Type", ct)
}
if cl != 0 {
w.Header().Set("Content-Length", strconv.FormatInt(cl, 10))
}
w.WriteHeader(http.StatusOK)
if r.Method != "HEAD" {
io.Copy(w, rc)
}
}

87
vendor/github.com/golang/gddo/httputil/transport.go generated vendored Normal file
View file

@ -0,0 +1,87 @@
// Copyright 2015 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// This file implements a http.RoundTripper that authenticates
// requests issued against api.github.com endpoint.
package httputil
import (
"net/http"
"net/url"
)
// AuthTransport is an implementation of http.RoundTripper that authenticates
// with the GitHub API.
//
// When both a token and client credentials are set, the latter is preferred.
type AuthTransport struct {
UserAgent string
GithubToken string
GithubClientID string
GithubClientSecret string
Base http.RoundTripper
}
// RoundTrip implements the http.RoundTripper interface.
func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
var reqCopy *http.Request
if t.UserAgent != "" {
reqCopy = copyRequest(req)
reqCopy.Header.Set("User-Agent", t.UserAgent)
}
if req.URL.Host == "api.github.com" && req.URL.Scheme == "https" {
switch {
case t.GithubClientID != "" && t.GithubClientSecret != "":
if reqCopy == nil {
reqCopy = copyRequest(req)
}
if reqCopy.URL.RawQuery == "" {
reqCopy.URL.RawQuery = "client_id=" + t.GithubClientID + "&client_secret=" + t.GithubClientSecret
} else {
reqCopy.URL.RawQuery += "&client_id=" + t.GithubClientID + "&client_secret=" + t.GithubClientSecret
}
case t.GithubToken != "":
if reqCopy == nil {
reqCopy = copyRequest(req)
}
reqCopy.Header.Set("Authorization", "token "+t.GithubToken)
}
}
if reqCopy != nil {
return t.base().RoundTrip(reqCopy)
}
return t.base().RoundTrip(req)
}
// CancelRequest cancels an in-flight request by closing its connection.
func (t *AuthTransport) CancelRequest(req *http.Request) {
type canceler interface {
CancelRequest(req *http.Request)
}
if cr, ok := t.base().(canceler); ok {
cr.CancelRequest(req)
}
}
func (t *AuthTransport) base() http.RoundTripper {
if t.Base != nil {
return t.Base
}
return http.DefaultTransport
}
func copyRequest(req *http.Request) *http.Request {
req2 := new(http.Request)
*req2 = *req
req2.URL = new(url.URL)
*req2.URL = *req.URL
req2.Header = make(http.Header, len(req.Header))
for k, s := range req.Header {
req2.Header[k] = append([]string(nil), s...)
}
return req2
}