github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/go-ethereum-master/swarm/api/http/server.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  /*
    18  A simple http server interface to Swarm
    19  */
    20  package http
    21  
    22  import (
    23  	"archive/tar"
    24  	"bufio"
    25  	"bytes"
    26  	"context"
    27  	"encoding/json"
    28  	"errors"
    29  	"fmt"
    30  	"io"
    31  	"io/ioutil"
    32  	"mime"
    33  	"mime/multipart"
    34  	"net/http"
    35  	"os"
    36  	"path"
    37  	"regexp"
    38  	"strconv"
    39  	"strings"
    40  	"time"
    41  
    42  	"github.com/ethereum/go-ethereum/common"
    43  	"github.com/ethereum/go-ethereum/common/hexutil"
    44  	"github.com/ethereum/go-ethereum/metrics"
    45  	"github.com/ethereum/go-ethereum/swarm/api"
    46  	"github.com/ethereum/go-ethereum/swarm/log"
    47  	"github.com/ethereum/go-ethereum/swarm/storage"
    48  	"github.com/ethereum/go-ethereum/swarm/storage/mru"
    49  	"github.com/pborman/uuid"
    50  	"github.com/rs/cors"
    51  )
    52  
    53  type resourceResponse struct {
    54  	Manifest storage.Address `json:"manifest"`
    55  	Resource string          `json:"resource"`
    56  	Update   storage.Address `json:"update"`
    57  }
    58  
    59  var (
    60  	postRawCount    = metrics.NewRegisteredCounter("api.http.post.raw.count", nil)
    61  	postRawFail     = metrics.NewRegisteredCounter("api.http.post.raw.fail", nil)
    62  	postFilesCount  = metrics.NewRegisteredCounter("api.http.post.files.count", nil)
    63  	postFilesFail   = metrics.NewRegisteredCounter("api.http.post.files.fail", nil)
    64  	deleteCount     = metrics.NewRegisteredCounter("api.http.delete.count", nil)
    65  	deleteFail      = metrics.NewRegisteredCounter("api.http.delete.fail", nil)
    66  	getCount        = metrics.NewRegisteredCounter("api.http.get.count", nil)
    67  	getFail         = metrics.NewRegisteredCounter("api.http.get.fail", nil)
    68  	getFileCount    = metrics.NewRegisteredCounter("api.http.get.file.count", nil)
    69  	getFileNotFound = metrics.NewRegisteredCounter("api.http.get.file.notfound", nil)
    70  	getFileFail     = metrics.NewRegisteredCounter("api.http.get.file.fail", nil)
    71  	getFilesCount   = metrics.NewRegisteredCounter("api.http.get.files.count", nil)
    72  	getFilesFail    = metrics.NewRegisteredCounter("api.http.get.files.fail", nil)
    73  	getListCount    = metrics.NewRegisteredCounter("api.http.get.list.count", nil)
    74  	getListFail     = metrics.NewRegisteredCounter("api.http.get.list.fail", nil)
    75  )
    76  
    77  // ServerConfig is the basic configuration needed for the HTTP server and also
    78  // includes CORS settings.
    79  type ServerConfig struct {
    80  	Addr       string
    81  	CorsString string
    82  }
    83  
    84  // browser API for registering bzz url scheme handlers:
    85  // https://developer.mozilla.org/en/docs/Web-based_protocol_handlers
    86  // electron (chromium) api for registering bzz url scheme handlers:
    87  // https://github.com/atom/electron/blob/master/docs/api/protocol.md
    88  
    89  // starts up http server
    90  func StartHTTPServer(api *api.API, config *ServerConfig) {
    91  	var allowedOrigins []string
    92  	for _, domain := range strings.Split(config.CorsString, ",") {
    93  		allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain))
    94  	}
    95  	c := cors.New(cors.Options{
    96  		AllowedOrigins: allowedOrigins,
    97  		AllowedMethods: []string{"POST", "GET", "DELETE", "PATCH", "PUT"},
    98  		MaxAge:         600,
    99  		AllowedHeaders: []string{"*"},
   100  	})
   101  	hdlr := c.Handler(NewServer(api))
   102  
   103  	go http.ListenAndServe(config.Addr, hdlr)
   104  }
   105  
   106  func NewServer(api *api.API) *Server {
   107  	return &Server{api}
   108  }
   109  
   110  type Server struct {
   111  	api *api.API
   112  }
   113  
   114  // Request wraps http.Request and also includes the parsed bzz URI
   115  type Request struct {
   116  	http.Request
   117  
   118  	uri  *api.URI
   119  	ruid string // request unique id
   120  }
   121  
   122  // HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request
   123  // body in swarm and returns the resulting storage address as a text/plain response
   124  func (s *Server) HandlePostRaw(ctx context.Context, w http.ResponseWriter, r *Request) {
   125  	log.Debug("handle.post.raw", "ruid", r.ruid)
   126  
   127  	postRawCount.Inc(1)
   128  
   129  	toEncrypt := false
   130  	if r.uri.Addr == "encrypt" {
   131  		toEncrypt = true
   132  	}
   133  
   134  	if r.uri.Path != "" {
   135  		postRawFail.Inc(1)
   136  		Respond(w, r, "raw POST request cannot contain a path", http.StatusBadRequest)
   137  		return
   138  	}
   139  
   140  	if r.uri.Addr != "" && r.uri.Addr != "encrypt" {
   141  		postRawFail.Inc(1)
   142  		Respond(w, r, "raw POST request addr can only be empty or \"encrypt\"", http.StatusBadRequest)
   143  		return
   144  	}
   145  
   146  	if r.Header.Get("Content-Length") == "" {
   147  		postRawFail.Inc(1)
   148  		Respond(w, r, "missing Content-Length header in request", http.StatusBadRequest)
   149  		return
   150  	}
   151  	addr, _, err := s.api.Store(ctx, r.Body, r.ContentLength, toEncrypt)
   152  	if err != nil {
   153  		postRawFail.Inc(1)
   154  		Respond(w, r, err.Error(), http.StatusInternalServerError)
   155  		return
   156  	}
   157  
   158  	log.Debug("stored content", "ruid", r.ruid, "key", addr)
   159  
   160  	w.Header().Set("Content-Type", "text/plain")
   161  	w.WriteHeader(http.StatusOK)
   162  	fmt.Fprint(w, addr)
   163  }
   164  
   165  // HandlePostFiles handles a POST request to
   166  // bzz:/<hash>/<path> which contains either a single file or multiple files
   167  // (either a tar archive or multipart form), adds those files either to an
   168  // existing manifest or to a new manifest under <path> and returns the
   169  // resulting manifest hash as a text/plain response
   170  func (s *Server) HandlePostFiles(ctx context.Context, w http.ResponseWriter, r *Request) {
   171  	log.Debug("handle.post.files", "ruid", r.ruid)
   172  
   173  	postFilesCount.Inc(1)
   174  	contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
   175  	if err != nil {
   176  		postFilesFail.Inc(1)
   177  		Respond(w, r, err.Error(), http.StatusBadRequest)
   178  		return
   179  	}
   180  
   181  	toEncrypt := false
   182  	if r.uri.Addr == "encrypt" {
   183  		toEncrypt = true
   184  	}
   185  
   186  	var addr storage.Address
   187  	if r.uri.Addr != "" && r.uri.Addr != "encrypt" {
   188  		addr, err = s.api.Resolve(ctx, r.uri)
   189  		if err != nil {
   190  			postFilesFail.Inc(1)
   191  			Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError)
   192  			return
   193  		}
   194  		log.Debug("resolved key", "ruid", r.ruid, "key", addr)
   195  	} else {
   196  		addr, err = s.api.NewManifest(ctx, toEncrypt)
   197  		if err != nil {
   198  			postFilesFail.Inc(1)
   199  			Respond(w, r, err.Error(), http.StatusInternalServerError)
   200  			return
   201  		}
   202  		log.Debug("new manifest", "ruid", r.ruid, "key", addr)
   203  	}
   204  
   205  	newAddr, err := s.updateManifest(ctx, addr, func(mw *api.ManifestWriter) error {
   206  		switch contentType {
   207  
   208  		case "application/x-tar":
   209  			return s.handleTarUpload(ctx, r, mw)
   210  
   211  		case "multipart/form-data":
   212  			return s.handleMultipartUpload(ctx, r, params["boundary"], mw)
   213  
   214  		default:
   215  			return s.handleDirectUpload(ctx, r, mw)
   216  		}
   217  	})
   218  	if err != nil {
   219  		postFilesFail.Inc(1)
   220  		Respond(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError)
   221  		return
   222  	}
   223  
   224  	log.Debug("stored content", "ruid", r.ruid, "key", newAddr)
   225  
   226  	w.Header().Set("Content-Type", "text/plain")
   227  	w.WriteHeader(http.StatusOK)
   228  	fmt.Fprint(w, newAddr)
   229  }
   230  
   231  func (s *Server) handleTarUpload(ctx context.Context, req *Request, mw *api.ManifestWriter) error {
   232  	log.Debug("handle.tar.upload", "ruid", req.ruid)
   233  	tr := tar.NewReader(req.Body)
   234  	for {
   235  		hdr, err := tr.Next()
   236  		if err == io.EOF {
   237  			return nil
   238  		} else if err != nil {
   239  			return fmt.Errorf("error reading tar stream: %s", err)
   240  		}
   241  
   242  		// only store regular files
   243  		if !hdr.FileInfo().Mode().IsRegular() {
   244  			continue
   245  		}
   246  
   247  		// add the entry under the path from the request
   248  		path := path.Join(req.uri.Path, hdr.Name)
   249  		entry := &api.ManifestEntry{
   250  			Path:        path,
   251  			ContentType: hdr.Xattrs["user.swarm.content-type"],
   252  			Mode:        hdr.Mode,
   253  			Size:        hdr.Size,
   254  			ModTime:     hdr.ModTime,
   255  		}
   256  		log.Debug("adding path to new manifest", "ruid", req.ruid, "bytes", entry.Size, "path", entry.Path)
   257  		contentKey, err := mw.AddEntry(ctx, tr, entry)
   258  		if err != nil {
   259  			return fmt.Errorf("error adding manifest entry from tar stream: %s", err)
   260  		}
   261  		log.Debug("stored content", "ruid", req.ruid, "key", contentKey)
   262  	}
   263  }
   264  
   265  func (s *Server) handleMultipartUpload(ctx context.Context, req *Request, boundary string, mw *api.ManifestWriter) error {
   266  	log.Debug("handle.multipart.upload", "ruid", req.ruid)
   267  	mr := multipart.NewReader(req.Body, boundary)
   268  	for {
   269  		part, err := mr.NextPart()
   270  		if err == io.EOF {
   271  			return nil
   272  		} else if err != nil {
   273  			return fmt.Errorf("error reading multipart form: %s", err)
   274  		}
   275  
   276  		var size int64
   277  		var reader io.Reader = part
   278  		if contentLength := part.Header.Get("Content-Length"); contentLength != "" {
   279  			size, err = strconv.ParseInt(contentLength, 10, 64)
   280  			if err != nil {
   281  				return fmt.Errorf("error parsing multipart content length: %s", err)
   282  			}
   283  			reader = part
   284  		} else {
   285  			// copy the part to a tmp file to get its size
   286  			tmp, err := ioutil.TempFile("", "swarm-multipart")
   287  			if err != nil {
   288  				return err
   289  			}
   290  			defer os.Remove(tmp.Name())
   291  			defer tmp.Close()
   292  			size, err = io.Copy(tmp, part)
   293  			if err != nil {
   294  				return fmt.Errorf("error copying multipart content: %s", err)
   295  			}
   296  			if _, err := tmp.Seek(0, io.SeekStart); err != nil {
   297  				return fmt.Errorf("error copying multipart content: %s", err)
   298  			}
   299  			reader = tmp
   300  		}
   301  
   302  		// add the entry under the path from the request
   303  		name := part.FileName()
   304  		if name == "" {
   305  			name = part.FormName()
   306  		}
   307  		path := path.Join(req.uri.Path, name)
   308  		entry := &api.ManifestEntry{
   309  			Path:        path,
   310  			ContentType: part.Header.Get("Content-Type"),
   311  			Size:        size,
   312  			ModTime:     time.Now(),
   313  		}
   314  		log.Debug("adding path to new manifest", "ruid", req.ruid, "bytes", entry.Size, "path", entry.Path)
   315  		contentKey, err := mw.AddEntry(ctx, reader, entry)
   316  		if err != nil {
   317  			return fmt.Errorf("error adding manifest entry from multipart form: %s", err)
   318  		}
   319  		log.Debug("stored content", "ruid", req.ruid, "key", contentKey)
   320  	}
   321  }
   322  
   323  func (s *Server) handleDirectUpload(ctx context.Context, req *Request, mw *api.ManifestWriter) error {
   324  	log.Debug("handle.direct.upload", "ruid", req.ruid)
   325  	key, err := mw.AddEntry(ctx, req.Body, &api.ManifestEntry{
   326  		Path:        req.uri.Path,
   327  		ContentType: req.Header.Get("Content-Type"),
   328  		Mode:        0644,
   329  		Size:        req.ContentLength,
   330  		ModTime:     time.Now(),
   331  	})
   332  	if err != nil {
   333  		return err
   334  	}
   335  	log.Debug("stored content", "ruid", req.ruid, "key", key)
   336  	return nil
   337  }
   338  
   339  // HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes
   340  // <path> from <manifest> and returns the resulting manifest hash as a
   341  // text/plain response
   342  func (s *Server) HandleDelete(ctx context.Context, w http.ResponseWriter, r *Request) {
   343  	log.Debug("handle.delete", "ruid", r.ruid)
   344  
   345  	deleteCount.Inc(1)
   346  	key, err := s.api.Resolve(ctx, r.uri)
   347  	if err != nil {
   348  		deleteFail.Inc(1)
   349  		Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusInternalServerError)
   350  		return
   351  	}
   352  
   353  	newKey, err := s.updateManifest(ctx, key, func(mw *api.ManifestWriter) error {
   354  		log.Debug(fmt.Sprintf("removing %s from manifest %s", r.uri.Path, key.Log()), "ruid", r.ruid)
   355  		return mw.RemoveEntry(r.uri.Path)
   356  	})
   357  	if err != nil {
   358  		deleteFail.Inc(1)
   359  		Respond(w, r, fmt.Sprintf("cannot update manifest: %s", err), http.StatusInternalServerError)
   360  		return
   361  	}
   362  
   363  	w.Header().Set("Content-Type", "text/plain")
   364  	w.WriteHeader(http.StatusOK)
   365  	fmt.Fprint(w, newKey)
   366  }
   367  
   368  // Parses a resource update post url to corresponding action
   369  // possible combinations:
   370  // /			add multihash update to existing hash
   371  // /raw 		add raw update to existing hash
   372  // /#			create new resource with first update as mulitihash
   373  // /raw/#		create new resource with first update raw
   374  func resourcePostMode(path string) (isRaw bool, frequency uint64, err error) {
   375  	re, err := regexp.Compile("^(raw)?/?([0-9]+)?$")
   376  	if err != nil {
   377  		return isRaw, frequency, err
   378  	}
   379  	m := re.FindAllStringSubmatch(path, 2)
   380  	var freqstr = "0"
   381  	if len(m) > 0 {
   382  		if m[0][1] != "" {
   383  			isRaw = true
   384  		}
   385  		if m[0][2] != "" {
   386  			freqstr = m[0][2]
   387  		}
   388  	} else if len(path) > 0 {
   389  		return isRaw, frequency, fmt.Errorf("invalid path")
   390  	}
   391  	frequency, err = strconv.ParseUint(freqstr, 10, 64)
   392  	return isRaw, frequency, err
   393  }
   394  
   395  // Handles creation of new mutable resources and adding updates to existing mutable resources
   396  // There are two types of updates available, "raw" and "multihash."
   397  // If the latter is used, a subsequent bzz:// GET call to the manifest of the resource will return
   398  // the page that the multihash is pointing to, as if it held a normal swarm content manifest
   399  //
   400  // The resource name will be verbatim what is passed as the address part of the url.
   401  // For example, if a POST is made to /bzz-resource:/foo.eth/raw/13 a new resource with frequency 13
   402  // and name "foo.eth" will be created
   403  func (s *Server) HandlePostResource(ctx context.Context, w http.ResponseWriter, r *Request) {
   404  	log.Debug("handle.post.resource", "ruid", r.ruid)
   405  	var err error
   406  	var addr storage.Address
   407  	var name string
   408  	var outdata []byte
   409  	isRaw, frequency, err := resourcePostMode(r.uri.Path)
   410  	if err != nil {
   411  		Respond(w, r, err.Error(), http.StatusBadRequest)
   412  		return
   413  	}
   414  
   415  	// new mutable resource creation will always have a frequency field larger than 0
   416  	if frequency > 0 {
   417  
   418  		name = r.uri.Addr
   419  
   420  		// the key is the content addressed root chunk holding mutable resource metadata information
   421  		addr, err = s.api.ResourceCreate(r.Context(), name, frequency)
   422  		if err != nil {
   423  			code, err2 := s.translateResourceError(w, r, "resource creation fail", err)
   424  
   425  			Respond(w, r, err2.Error(), code)
   426  			return
   427  		}
   428  
   429  		// we create a manifest so we can retrieve the resource with bzz:// later
   430  		// this manifest has a special "resource type" manifest, and its hash is the key of the mutable resource
   431  		// root chunk
   432  		m, err := s.api.NewResourceManifest(ctx, addr.Hex())
   433  		if err != nil {
   434  			Respond(w, r, fmt.Sprintf("failed to create resource manifest: %v", err), http.StatusInternalServerError)
   435  			return
   436  		}
   437  
   438  		// the key to the manifest will be passed back to the client
   439  		// the client can access the root chunk key directly through its Hash member
   440  		// the manifest key should be set as content in the resolver of the ENS name
   441  		// \TODO update manifest key automatically in ENS
   442  		outdata, err = json.Marshal(m)
   443  		if err != nil {
   444  			Respond(w, r, fmt.Sprintf("failed to create json response: %s", err), http.StatusInternalServerError)
   445  			return
   446  		}
   447  	} else {
   448  		// to update the resource through http we need to retrieve the key for the mutable resource root chunk
   449  		// that means that we retrieve the manifest and inspect its Hash member.
   450  		manifestAddr := r.uri.Address()
   451  		if manifestAddr == nil {
   452  			manifestAddr, err = s.api.Resolve(ctx, r.uri)
   453  			if err != nil {
   454  				getFail.Inc(1)
   455  				Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
   456  				return
   457  			}
   458  		} else {
   459  			w.Header().Set("Cache-Control", "max-age=2147483648")
   460  		}
   461  
   462  		// get the root chunk key from the manifest
   463  		addr, err = s.api.ResolveResourceManifest(ctx, manifestAddr)
   464  		if err != nil {
   465  			getFail.Inc(1)
   466  			Respond(w, r, fmt.Sprintf("error resolving resource root chunk for %s: %s", r.uri.Addr, err), http.StatusNotFound)
   467  			return
   468  		}
   469  
   470  		log.Debug("handle.post.resource: resolved", "ruid", r.ruid, "manifestkey", manifestAddr, "rootchunkkey", addr)
   471  
   472  		name, _, err = s.api.ResourceLookup(r.Context(), addr, 0, 0, &mru.LookupParams{})
   473  		if err != nil {
   474  			Respond(w, r, err.Error(), http.StatusNotFound)
   475  			return
   476  		}
   477  	}
   478  
   479  	// Creation and update must send data aswell. This data constitutes the update data itself.
   480  	data, err := ioutil.ReadAll(r.Body)
   481  	if err != nil {
   482  		Respond(w, r, err.Error(), http.StatusInternalServerError)
   483  		return
   484  	}
   485  
   486  	// Multihash will be passed as hex-encoded data, so we need to parse this to bytes
   487  	if isRaw {
   488  		_, _, _, err = s.api.ResourceUpdate(r.Context(), name, data)
   489  		if err != nil {
   490  			Respond(w, r, err.Error(), http.StatusBadRequest)
   491  			return
   492  		}
   493  	} else {
   494  		bytesdata, err := hexutil.Decode(string(data))
   495  		if err != nil {
   496  			Respond(w, r, err.Error(), http.StatusBadRequest)
   497  			return
   498  		}
   499  		_, _, _, err = s.api.ResourceUpdateMultihash(r.Context(), name, bytesdata)
   500  		if err != nil {
   501  			Respond(w, r, err.Error(), http.StatusBadRequest)
   502  			return
   503  		}
   504  	}
   505  
   506  	// If we have data to return, write this now
   507  	// \TODO there should always be data to return here
   508  	if len(outdata) > 0 {
   509  		w.Header().Add("Content-type", "text/plain")
   510  		w.WriteHeader(http.StatusOK)
   511  		fmt.Fprint(w, string(outdata))
   512  		return
   513  	}
   514  	w.WriteHeader(http.StatusOK)
   515  }
   516  
   517  // Retrieve mutable resource updates:
   518  // bzz-resource://<id> - get latest update
   519  // bzz-resource://<id>/<n> - get latest update on period n
   520  // bzz-resource://<id>/<n>/<m> - get update version m of period n
   521  // <id> = ens name or hash
   522  func (s *Server) HandleGetResource(ctx context.Context, w http.ResponseWriter, r *Request) {
   523  	s.handleGetResource(ctx, w, r)
   524  }
   525  
   526  // TODO: Enable pass maxPeriod parameter
   527  func (s *Server) handleGetResource(ctx context.Context, w http.ResponseWriter, r *Request) {
   528  	log.Debug("handle.get.resource", "ruid", r.ruid)
   529  	var err error
   530  
   531  	// resolve the content key.
   532  	manifestAddr := r.uri.Address()
   533  	if manifestAddr == nil {
   534  		manifestAddr, err = s.api.Resolve(ctx, r.uri)
   535  		if err != nil {
   536  			getFail.Inc(1)
   537  			Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
   538  			return
   539  		}
   540  	} else {
   541  		w.Header().Set("Cache-Control", "max-age=2147483648")
   542  	}
   543  
   544  	// get the root chunk key from the manifest
   545  	key, err := s.api.ResolveResourceManifest(ctx, manifestAddr)
   546  	if err != nil {
   547  		getFail.Inc(1)
   548  		Respond(w, r, fmt.Sprintf("error resolving resource root chunk for %s: %s", r.uri.Addr, err), http.StatusNotFound)
   549  		return
   550  	}
   551  
   552  	log.Debug("handle.get.resource: resolved", "ruid", r.ruid, "manifestkey", manifestAddr, "rootchunk key", key)
   553  
   554  	// determine if the query specifies period and version
   555  	var params []string
   556  	if len(r.uri.Path) > 0 {
   557  		params = strings.Split(r.uri.Path, "/")
   558  	}
   559  	var name string
   560  	var period uint64
   561  	var version uint64
   562  	var data []byte
   563  	now := time.Now()
   564  
   565  	switch len(params) {
   566  	case 0: // latest only
   567  		name, data, err = s.api.ResourceLookup(r.Context(), key, 0, 0, nil)
   568  	case 2: // specific period and version
   569  		version, err = strconv.ParseUint(params[1], 10, 32)
   570  		if err != nil {
   571  			break
   572  		}
   573  		period, err = strconv.ParseUint(params[0], 10, 32)
   574  		if err != nil {
   575  			break
   576  		}
   577  		name, data, err = s.api.ResourceLookup(r.Context(), key, uint32(period), uint32(version), nil)
   578  	case 1: // last version of specific period
   579  		period, err = strconv.ParseUint(params[0], 10, 32)
   580  		if err != nil {
   581  			break
   582  		}
   583  		name, data, err = s.api.ResourceLookup(r.Context(), key, uint32(period), uint32(version), nil)
   584  	default: // bogus
   585  		err = mru.NewError(storage.ErrInvalidValue, "invalid mutable resource request")
   586  	}
   587  
   588  	// any error from the switch statement will end up here
   589  	if err != nil {
   590  		code, err2 := s.translateResourceError(w, r, "mutable resource lookup fail", err)
   591  		Respond(w, r, err2.Error(), code)
   592  		return
   593  	}
   594  
   595  	// All ok, serve the retrieved update
   596  	log.Debug("Found update", "name", name, "ruid", r.ruid)
   597  	w.Header().Set("Content-Type", "application/octet-stream")
   598  	http.ServeContent(w, &r.Request, "", now, bytes.NewReader(data))
   599  }
   600  
   601  func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supErr string, err error) (int, error) {
   602  	code := 0
   603  	defaultErr := fmt.Errorf("%s: %v", supErr, err)
   604  	rsrcErr, ok := err.(*mru.Error)
   605  	if !ok && rsrcErr != nil {
   606  		code = rsrcErr.Code()
   607  	}
   608  	switch code {
   609  	case storage.ErrInvalidValue:
   610  		return http.StatusBadRequest, defaultErr
   611  	case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn, storage.ErrInit:
   612  		return http.StatusNotFound, defaultErr
   613  	case storage.ErrUnauthorized, storage.ErrInvalidSignature:
   614  		return http.StatusUnauthorized, defaultErr
   615  	case storage.ErrDataOverflow:
   616  		return http.StatusRequestEntityTooLarge, defaultErr
   617  	}
   618  
   619  	return http.StatusInternalServerError, defaultErr
   620  }
   621  
   622  // HandleGet handles a GET request to
   623  // - bzz-raw://<key> and responds with the raw content stored at the
   624  //   given storage key
   625  // - bzz-hash://<key> and responds with the hash of the content stored
   626  //   at the given storage key as a text/plain response
   627  func (s *Server) HandleGet(ctx context.Context, w http.ResponseWriter, r *Request) {
   628  	log.Debug("handle.get", "ruid", r.ruid, "uri", r.uri)
   629  	getCount.Inc(1)
   630  	var err error
   631  	addr := r.uri.Address()
   632  	if addr == nil {
   633  		addr, err = s.api.Resolve(ctx, r.uri)
   634  		if err != nil {
   635  			getFail.Inc(1)
   636  			Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
   637  			return
   638  		}
   639  	} else {
   640  		w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable.
   641  	}
   642  
   643  	log.Debug("handle.get: resolved", "ruid", r.ruid, "key", addr)
   644  
   645  	// if path is set, interpret <key> as a manifest and return the
   646  	// raw entry at the given path
   647  	if r.uri.Path != "" {
   648  		walker, err := s.api.NewManifestWalker(ctx, addr, nil)
   649  		if err != nil {
   650  			getFail.Inc(1)
   651  			Respond(w, r, fmt.Sprintf("%s is not a manifest", addr), http.StatusBadRequest)
   652  			return
   653  		}
   654  		var entry *api.ManifestEntry
   655  		walker.Walk(func(e *api.ManifestEntry) error {
   656  			// if the entry matches the path, set entry and stop
   657  			// the walk
   658  			if e.Path == r.uri.Path {
   659  				entry = e
   660  				// return an error to cancel the walk
   661  				return errors.New("found")
   662  			}
   663  
   664  			// ignore non-manifest files
   665  			if e.ContentType != api.ManifestType {
   666  				return nil
   667  			}
   668  
   669  			// if the manifest's path is a prefix of the
   670  			// requested path, recurse into it by returning
   671  			// nil and continuing the walk
   672  			if strings.HasPrefix(r.uri.Path, e.Path) {
   673  				return nil
   674  			}
   675  
   676  			return api.ErrSkipManifest
   677  		})
   678  		if entry == nil {
   679  			getFail.Inc(1)
   680  			Respond(w, r, fmt.Sprintf("manifest entry could not be loaded"), http.StatusNotFound)
   681  			return
   682  		}
   683  		addr = storage.Address(common.Hex2Bytes(entry.Hash))
   684  	}
   685  	etag := common.Bytes2Hex(addr)
   686  	noneMatchEtag := r.Header.Get("If-None-Match")
   687  	w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key.
   688  	if noneMatchEtag != "" {
   689  		if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), addr) {
   690  			Respond(w, r, "Not Modified", http.StatusNotModified)
   691  			return
   692  		}
   693  	}
   694  
   695  	// check the root chunk exists by retrieving the file's size
   696  	reader, isEncrypted := s.api.Retrieve(ctx, addr)
   697  	if _, err := reader.Size(nil); err != nil {
   698  		getFail.Inc(1)
   699  		Respond(w, r, fmt.Sprintf("root chunk not found %s: %s", addr, err), http.StatusNotFound)
   700  		return
   701  	}
   702  
   703  	w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted))
   704  
   705  	switch {
   706  	case r.uri.Raw():
   707  		// allow the request to overwrite the content type using a query
   708  		// parameter
   709  		contentType := "application/octet-stream"
   710  		if typ := r.URL.Query().Get("content_type"); typ != "" {
   711  			contentType = typ
   712  		}
   713  		w.Header().Set("Content-Type", contentType)
   714  		http.ServeContent(w, &r.Request, "", time.Now(), reader)
   715  	case r.uri.Hash():
   716  		w.Header().Set("Content-Type", "text/plain")
   717  		w.WriteHeader(http.StatusOK)
   718  		fmt.Fprint(w, addr)
   719  	}
   720  }
   721  
   722  // HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept
   723  // header of "application/x-tar" and returns a tar stream of all files
   724  // contained in the manifest
   725  func (s *Server) HandleGetFiles(ctx context.Context, w http.ResponseWriter, r *Request) {
   726  	log.Debug("handle.get.files", "ruid", r.ruid, "uri", r.uri)
   727  	getFilesCount.Inc(1)
   728  	if r.uri.Path != "" {
   729  		getFilesFail.Inc(1)
   730  		Respond(w, r, "files request cannot contain a path", http.StatusBadRequest)
   731  		return
   732  	}
   733  
   734  	addr, err := s.api.Resolve(ctx, r.uri)
   735  	if err != nil {
   736  		getFilesFail.Inc(1)
   737  		Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
   738  		return
   739  	}
   740  	log.Debug("handle.get.files: resolved", "ruid", r.ruid, "key", addr)
   741  
   742  	walker, err := s.api.NewManifestWalker(ctx, addr, nil)
   743  	if err != nil {
   744  		getFilesFail.Inc(1)
   745  		Respond(w, r, err.Error(), http.StatusInternalServerError)
   746  		return
   747  	}
   748  
   749  	tw := tar.NewWriter(w)
   750  	defer tw.Close()
   751  	w.Header().Set("Content-Type", "application/x-tar")
   752  	w.WriteHeader(http.StatusOK)
   753  
   754  	err = walker.Walk(func(entry *api.ManifestEntry) error {
   755  		// ignore manifests (walk will recurse into them)
   756  		if entry.ContentType == api.ManifestType {
   757  			return nil
   758  		}
   759  
   760  		// retrieve the entry's key and size
   761  		reader, isEncrypted := s.api.Retrieve(ctx, storage.Address(common.Hex2Bytes(entry.Hash)))
   762  		size, err := reader.Size(nil)
   763  		if err != nil {
   764  			return err
   765  		}
   766  		w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted))
   767  
   768  		// write a tar header for the entry
   769  		hdr := &tar.Header{
   770  			Name:    entry.Path,
   771  			Mode:    entry.Mode,
   772  			Size:    size,
   773  			ModTime: entry.ModTime,
   774  			Xattrs: map[string]string{
   775  				"user.swarm.content-type": entry.ContentType,
   776  			},
   777  		}
   778  		if err := tw.WriteHeader(hdr); err != nil {
   779  			return err
   780  		}
   781  
   782  		// copy the file into the tar stream
   783  		n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size))
   784  		if err != nil {
   785  			return err
   786  		} else if n != size {
   787  			return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n)
   788  		}
   789  
   790  		return nil
   791  	})
   792  	if err != nil {
   793  		getFilesFail.Inc(1)
   794  		log.Error(fmt.Sprintf("error generating tar stream: %s", err))
   795  	}
   796  }
   797  
   798  // HandleGetList handles a GET request to bzz-list:/<manifest>/<path> and returns
   799  // a list of all files contained in <manifest> under <path> grouped into
   800  // common prefixes using "/" as a delimiter
   801  func (s *Server) HandleGetList(ctx context.Context, w http.ResponseWriter, r *Request) {
   802  	log.Debug("handle.get.list", "ruid", r.ruid, "uri", r.uri)
   803  	getListCount.Inc(1)
   804  	// ensure the root path has a trailing slash so that relative URLs work
   805  	if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") {
   806  		http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently)
   807  		return
   808  	}
   809  
   810  	addr, err := s.api.Resolve(ctx, r.uri)
   811  	if err != nil {
   812  		getListFail.Inc(1)
   813  		Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
   814  		return
   815  	}
   816  	log.Debug("handle.get.list: resolved", "ruid", r.ruid, "key", addr)
   817  
   818  	list, err := s.getManifestList(ctx, addr, r.uri.Path)
   819  
   820  	if err != nil {
   821  		getListFail.Inc(1)
   822  		Respond(w, r, err.Error(), http.StatusInternalServerError)
   823  		return
   824  	}
   825  
   826  	// if the client wants HTML (e.g. a browser) then render the list as a
   827  	// HTML index with relative URLs
   828  	if strings.Contains(r.Header.Get("Accept"), "text/html") {
   829  		w.Header().Set("Content-Type", "text/html")
   830  		err := htmlListTemplate.Execute(w, &htmlListData{
   831  			URI: &api.URI{
   832  				Scheme: "bzz",
   833  				Addr:   r.uri.Addr,
   834  				Path:   r.uri.Path,
   835  			},
   836  			List: &list,
   837  		})
   838  		if err != nil {
   839  			getListFail.Inc(1)
   840  			log.Error(fmt.Sprintf("error rendering list HTML: %s", err))
   841  		}
   842  		return
   843  	}
   844  
   845  	w.Header().Set("Content-Type", "application/json")
   846  	json.NewEncoder(w).Encode(&list)
   847  }
   848  
   849  func (s *Server) getManifestList(ctx context.Context, addr storage.Address, prefix string) (list api.ManifestList, err error) {
   850  	walker, err := s.api.NewManifestWalker(ctx, addr, nil)
   851  	if err != nil {
   852  		return
   853  	}
   854  
   855  	err = walker.Walk(func(entry *api.ManifestEntry) error {
   856  		// handle non-manifest files
   857  		if entry.ContentType != api.ManifestType {
   858  			// ignore the file if it doesn't have the specified prefix
   859  			if !strings.HasPrefix(entry.Path, prefix) {
   860  				return nil
   861  			}
   862  
   863  			// if the path after the prefix contains a slash, add a
   864  			// common prefix to the list, otherwise add the entry
   865  			suffix := strings.TrimPrefix(entry.Path, prefix)
   866  			if index := strings.Index(suffix, "/"); index > -1 {
   867  				list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1])
   868  				return nil
   869  			}
   870  			if entry.Path == "" {
   871  				entry.Path = "/"
   872  			}
   873  			list.Entries = append(list.Entries, entry)
   874  			return nil
   875  		}
   876  
   877  		// if the manifest's path is a prefix of the specified prefix
   878  		// then just recurse into the manifest by returning nil and
   879  		// continuing the walk
   880  		if strings.HasPrefix(prefix, entry.Path) {
   881  			return nil
   882  		}
   883  
   884  		// if the manifest's path has the specified prefix, then if the
   885  		// path after the prefix contains a slash, add a common prefix
   886  		// to the list and skip the manifest, otherwise recurse into
   887  		// the manifest by returning nil and continuing the walk
   888  		if strings.HasPrefix(entry.Path, prefix) {
   889  			suffix := strings.TrimPrefix(entry.Path, prefix)
   890  			if index := strings.Index(suffix, "/"); index > -1 {
   891  				list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1])
   892  				return api.ErrSkipManifest
   893  			}
   894  			return nil
   895  		}
   896  
   897  		// the manifest neither has the prefix or needs recursing in to
   898  		// so just skip it
   899  		return api.ErrSkipManifest
   900  	})
   901  
   902  	return list, nil
   903  }
   904  
   905  // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds
   906  // with the content of the file at <path> from the given <manifest>
   907  func (s *Server) HandleGetFile(ctx context.Context, w http.ResponseWriter, r *Request) {
   908  	log.Debug("handle.get.file", "ruid", r.ruid)
   909  	getFileCount.Inc(1)
   910  	// ensure the root path has a trailing slash so that relative URLs work
   911  	if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") {
   912  		http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently)
   913  		return
   914  	}
   915  	var err error
   916  	manifestAddr := r.uri.Address()
   917  
   918  	if manifestAddr == nil {
   919  		manifestAddr, err = s.api.Resolve(ctx, r.uri)
   920  		if err != nil {
   921  			getFileFail.Inc(1)
   922  			Respond(w, r, fmt.Sprintf("cannot resolve %s: %s", r.uri.Addr, err), http.StatusNotFound)
   923  			return
   924  		}
   925  	} else {
   926  		w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable.
   927  	}
   928  
   929  	log.Debug("handle.get.file: resolved", "ruid", r.ruid, "key", manifestAddr)
   930  
   931  	reader, contentType, status, contentKey, err := s.api.Get(ctx, manifestAddr, r.uri.Path)
   932  
   933  	etag := common.Bytes2Hex(contentKey)
   934  	noneMatchEtag := r.Header.Get("If-None-Match")
   935  	w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to actual content key.
   936  	if noneMatchEtag != "" {
   937  		if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) {
   938  			Respond(w, r, "Not Modified", http.StatusNotModified)
   939  			return
   940  		}
   941  	}
   942  
   943  	if err != nil {
   944  		switch status {
   945  		case http.StatusNotFound:
   946  			getFileNotFound.Inc(1)
   947  			Respond(w, r, err.Error(), http.StatusNotFound)
   948  		default:
   949  			getFileFail.Inc(1)
   950  			Respond(w, r, err.Error(), http.StatusInternalServerError)
   951  		}
   952  		return
   953  	}
   954  
   955  	//the request results in ambiguous files
   956  	//e.g. /read with readme.md and readinglist.txt available in manifest
   957  	if status == http.StatusMultipleChoices {
   958  		list, err := s.getManifestList(ctx, manifestAddr, r.uri.Path)
   959  
   960  		if err != nil {
   961  			getFileFail.Inc(1)
   962  			Respond(w, r, err.Error(), http.StatusInternalServerError)
   963  			return
   964  		}
   965  
   966  		log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", r.ruid)
   967  		//show a nice page links to available entries
   968  		ShowMultipleChoices(w, r, list)
   969  		return
   970  	}
   971  
   972  	// check the root chunk exists by retrieving the file's size
   973  	if _, err := reader.Size(nil); err != nil {
   974  		getFileNotFound.Inc(1)
   975  		Respond(w, r, fmt.Sprintf("file not found %s: %s", r.uri, err), http.StatusNotFound)
   976  		return
   977  	}
   978  
   979  	w.Header().Set("Content-Type", contentType)
   980  	http.ServeContent(w, &r.Request, "", time.Now(), newBufferedReadSeeker(reader, getFileBufferSize))
   981  }
   982  
   983  // The size of buffer used for bufio.Reader on LazyChunkReader passed to
   984  // http.ServeContent in HandleGetFile.
   985  // Warning: This value influences the number of chunk requests and chunker join goroutines
   986  // per file request.
   987  // Recommended value is 4 times the io.Copy default buffer value which is 32kB.
   988  const getFileBufferSize = 4 * 32 * 1024
   989  
   990  // bufferedReadSeeker wraps bufio.Reader to expose Seek method
   991  // from the provied io.ReadSeeker in newBufferedReadSeeker.
   992  type bufferedReadSeeker struct {
   993  	r io.Reader
   994  	s io.Seeker
   995  }
   996  
   997  // newBufferedReadSeeker creates a new instance of bufferedReadSeeker,
   998  // out of io.ReadSeeker. Argument `size` is the size of the read buffer.
   999  func newBufferedReadSeeker(readSeeker io.ReadSeeker, size int) bufferedReadSeeker {
  1000  	return bufferedReadSeeker{
  1001  		r: bufio.NewReaderSize(readSeeker, size),
  1002  		s: readSeeker,
  1003  	}
  1004  }
  1005  
  1006  func (b bufferedReadSeeker) Read(p []byte) (n int, err error) {
  1007  	return b.r.Read(p)
  1008  }
  1009  
  1010  func (b bufferedReadSeeker) Seek(offset int64, whence int) (int64, error) {
  1011  	return b.s.Seek(offset, whence)
  1012  }
  1013  
  1014  func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
  1015  	ctx := context.TODO()
  1016  
  1017  	defer metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.time", r.Method), nil).UpdateSince(time.Now())
  1018  	req := &Request{Request: *r, ruid: uuid.New()[:8]}
  1019  	metrics.GetOrRegisterCounter(fmt.Sprintf("http.request.%s", r.Method), nil).Inc(1)
  1020  	log.Info("serving request", "ruid", req.ruid, "method", r.Method, "url", r.RequestURI)
  1021  
  1022  	// wrapping the ResponseWriter, so that we get the response code set by http.ServeContent
  1023  	w := newLoggingResponseWriter(rw)
  1024  
  1025  	if r.RequestURI == "/" && strings.Contains(r.Header.Get("Accept"), "text/html") {
  1026  
  1027  		err := landingPageTemplate.Execute(w, nil)
  1028  		if err != nil {
  1029  			log.Error(fmt.Sprintf("error rendering landing page: %s", err))
  1030  		}
  1031  		return
  1032  	}
  1033  
  1034  	if r.URL.Path == "/robots.txt" {
  1035  		w.Header().Set("Last-Modified", time.Now().Format(http.TimeFormat))
  1036  		fmt.Fprintf(w, "User-agent: *\nDisallow: /")
  1037  		return
  1038  	}
  1039  
  1040  	if r.RequestURI == "/" && strings.Contains(r.Header.Get("Accept"), "application/json") {
  1041  		w.Header().Set("Content-Type", "application/json")
  1042  		w.WriteHeader(http.StatusOK)
  1043  		json.NewEncoder(w).Encode("Welcome to Swarm!")
  1044  		return
  1045  	}
  1046  
  1047  	uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/"))
  1048  	if err != nil {
  1049  		Respond(w, req, fmt.Sprintf("invalid URI %q", r.URL.Path), http.StatusBadRequest)
  1050  		return
  1051  	}
  1052  
  1053  	req.uri = uri
  1054  
  1055  	log.Debug("parsed request path", "ruid", req.ruid, "method", req.Method, "uri.Addr", req.uri.Addr, "uri.Path", req.uri.Path, "uri.Scheme", req.uri.Scheme)
  1056  
  1057  	switch r.Method {
  1058  	case "POST":
  1059  		if uri.Raw() {
  1060  			log.Debug("handlePostRaw")
  1061  			s.HandlePostRaw(ctx, w, req)
  1062  		} else if uri.Resource() {
  1063  			log.Debug("handlePostResource")
  1064  			s.HandlePostResource(ctx, w, req)
  1065  		} else if uri.Immutable() || uri.List() || uri.Hash() {
  1066  			log.Debug("POST not allowed on immutable, list or hash")
  1067  			Respond(w, req, fmt.Sprintf("POST method on scheme %s not allowed", uri.Scheme), http.StatusMethodNotAllowed)
  1068  		} else {
  1069  			log.Debug("handlePostFiles")
  1070  			s.HandlePostFiles(ctx, w, req)
  1071  		}
  1072  
  1073  	case "PUT":
  1074  		Respond(w, req, fmt.Sprintf("PUT method to %s not allowed", uri), http.StatusBadRequest)
  1075  		return
  1076  
  1077  	case "DELETE":
  1078  		if uri.Raw() {
  1079  			Respond(w, req, fmt.Sprintf("DELETE method to %s not allowed", uri), http.StatusBadRequest)
  1080  			return
  1081  		}
  1082  		s.HandleDelete(ctx, w, req)
  1083  
  1084  	case "GET":
  1085  
  1086  		if uri.Resource() {
  1087  			s.HandleGetResource(ctx, w, req)
  1088  			return
  1089  		}
  1090  
  1091  		if uri.Raw() || uri.Hash() {
  1092  			s.HandleGet(ctx, w, req)
  1093  			return
  1094  		}
  1095  
  1096  		if uri.List() {
  1097  			s.HandleGetList(ctx, w, req)
  1098  			return
  1099  		}
  1100  
  1101  		if r.Header.Get("Accept") == "application/x-tar" {
  1102  			s.HandleGetFiles(ctx, w, req)
  1103  			return
  1104  		}
  1105  
  1106  		s.HandleGetFile(ctx, w, req)
  1107  
  1108  	default:
  1109  		Respond(w, req, fmt.Sprintf("%s method is not supported", r.Method), http.StatusMethodNotAllowed)
  1110  	}
  1111  
  1112  	log.Info("served response", "ruid", req.ruid, "code", w.statusCode)
  1113  }
  1114  
  1115  func (s *Server) updateManifest(ctx context.Context, addr storage.Address, update func(mw *api.ManifestWriter) error) (storage.Address, error) {
  1116  	mw, err := s.api.NewManifestWriter(ctx, addr, nil)
  1117  	if err != nil {
  1118  		return nil, err
  1119  	}
  1120  
  1121  	if err := update(mw); err != nil {
  1122  		return nil, err
  1123  	}
  1124  
  1125  	addr, err = mw.Store()
  1126  	if err != nil {
  1127  		return nil, err
  1128  	}
  1129  	log.Debug(fmt.Sprintf("generated manifest %s", addr))
  1130  	return addr, nil
  1131  }
  1132  
  1133  type loggingResponseWriter struct {
  1134  	http.ResponseWriter
  1135  	statusCode int
  1136  }
  1137  
  1138  func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
  1139  	return &loggingResponseWriter{w, http.StatusOK}
  1140  }
  1141  
  1142  func (lrw *loggingResponseWriter) WriteHeader(code int) {
  1143  	lrw.statusCode = code
  1144  	lrw.ResponseWriter.WriteHeader(code)
  1145  }