github.com/alanchchen/go-ethereum@v1.6.6-0.20170601190819-6171d01b1195/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  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"io/ioutil"
    29  	"mime"
    30  	"mime/multipart"
    31  	"net/http"
    32  	"os"
    33  	"path"
    34  	"strconv"
    35  	"strings"
    36  	"time"
    37  
    38  	"github.com/ethereum/go-ethereum/common"
    39  	"github.com/ethereum/go-ethereum/log"
    40  	"github.com/ethereum/go-ethereum/swarm/api"
    41  	"github.com/ethereum/go-ethereum/swarm/storage"
    42  	"github.com/rs/cors"
    43  )
    44  
    45  // ServerConfig is the basic configuration needed for the HTTP server and also
    46  // includes CORS settings.
    47  type ServerConfig struct {
    48  	Addr       string
    49  	CorsString string
    50  }
    51  
    52  // browser API for registering bzz url scheme handlers:
    53  // https://developer.mozilla.org/en/docs/Web-based_protocol_handlers
    54  // electron (chromium) api for registering bzz url scheme handlers:
    55  // https://github.com/atom/electron/blob/master/docs/api/protocol.md
    56  
    57  // starts up http server
    58  func StartHttpServer(api *api.Api, config *ServerConfig) {
    59  	var allowedOrigins []string
    60  	for _, domain := range strings.Split(config.CorsString, ",") {
    61  		allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain))
    62  	}
    63  	c := cors.New(cors.Options{
    64  		AllowedOrigins: allowedOrigins,
    65  		AllowedMethods: []string{"POST", "GET", "DELETE", "PATCH", "PUT"},
    66  		MaxAge:         600,
    67  		AllowedHeaders: []string{"*"},
    68  	})
    69  	hdlr := c.Handler(NewServer(api))
    70  
    71  	go http.ListenAndServe(config.Addr, hdlr)
    72  }
    73  
    74  func NewServer(api *api.Api) *Server {
    75  	return &Server{api}
    76  }
    77  
    78  type Server struct {
    79  	api *api.Api
    80  }
    81  
    82  // Request wraps http.Request and also includes the parsed bzz URI
    83  type Request struct {
    84  	http.Request
    85  
    86  	uri *api.URI
    87  }
    88  
    89  // HandlePostRaw handles a POST request to a raw bzzr:/ URI, stores the request
    90  // body in swarm and returns the resulting storage key as a text/plain response
    91  func (s *Server) HandlePostRaw(w http.ResponseWriter, r *Request) {
    92  	if r.uri.Path != "" {
    93  		s.BadRequest(w, r, "raw POST request cannot contain a path")
    94  		return
    95  	}
    96  
    97  	if r.Header.Get("Content-Length") == "" {
    98  		s.BadRequest(w, r, "missing Content-Length header in request")
    99  		return
   100  	}
   101  
   102  	key, err := s.api.Store(r.Body, r.ContentLength, nil)
   103  	if err != nil {
   104  		s.Error(w, r, err)
   105  		return
   106  	}
   107  	s.logDebug("content for %s stored", key.Log())
   108  
   109  	w.Header().Set("Content-Type", "text/plain")
   110  	w.WriteHeader(http.StatusOK)
   111  	fmt.Fprint(w, key)
   112  }
   113  
   114  // HandlePostFiles handles a POST request (or deprecated PUT request) to
   115  // bzz:/<hash>/<path> which contains either a single file or multiple files
   116  // (either a tar archive or multipart form), adds those files either to an
   117  // existing manifest or to a new manifest under <path> and returns the
   118  // resulting manifest hash as a text/plain response
   119  func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) {
   120  	contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
   121  	if err != nil {
   122  		s.BadRequest(w, r, err.Error())
   123  		return
   124  	}
   125  
   126  	var key storage.Key
   127  	if r.uri.Addr != "" {
   128  		key, err = s.api.Resolve(r.uri)
   129  		if err != nil {
   130  			s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
   131  			return
   132  		}
   133  	} else {
   134  		key, err = s.api.NewManifest()
   135  		if err != nil {
   136  			s.Error(w, r, err)
   137  			return
   138  		}
   139  	}
   140  
   141  	newKey, err := s.updateManifest(key, func(mw *api.ManifestWriter) error {
   142  		switch contentType {
   143  
   144  		case "application/x-tar":
   145  			return s.handleTarUpload(r, mw)
   146  
   147  		case "multipart/form-data":
   148  			return s.handleMultipartUpload(r, params["boundary"], mw)
   149  
   150  		default:
   151  			return s.handleDirectUpload(r, mw)
   152  		}
   153  	})
   154  	if err != nil {
   155  		s.Error(w, r, fmt.Errorf("error creating manifest: %s", err))
   156  		return
   157  	}
   158  
   159  	w.Header().Set("Content-Type", "text/plain")
   160  	w.WriteHeader(http.StatusOK)
   161  	fmt.Fprint(w, newKey)
   162  }
   163  
   164  func (s *Server) handleTarUpload(req *Request, mw *api.ManifestWriter) error {
   165  	tr := tar.NewReader(req.Body)
   166  	for {
   167  		hdr, err := tr.Next()
   168  		if err == io.EOF {
   169  			return nil
   170  		} else if err != nil {
   171  			return fmt.Errorf("error reading tar stream: %s", err)
   172  		}
   173  
   174  		// only store regular files
   175  		if !hdr.FileInfo().Mode().IsRegular() {
   176  			continue
   177  		}
   178  
   179  		// add the entry under the path from the request
   180  		path := path.Join(req.uri.Path, hdr.Name)
   181  		entry := &api.ManifestEntry{
   182  			Path:        path,
   183  			ContentType: hdr.Xattrs["user.swarm.content-type"],
   184  			Mode:        hdr.Mode,
   185  			Size:        hdr.Size,
   186  			ModTime:     hdr.ModTime,
   187  		}
   188  		s.logDebug("adding %s (%d bytes) to new manifest", entry.Path, entry.Size)
   189  		contentKey, err := mw.AddEntry(tr, entry)
   190  		if err != nil {
   191  			return fmt.Errorf("error adding manifest entry from tar stream: %s", err)
   192  		}
   193  		s.logDebug("content for %s stored", contentKey.Log())
   194  	}
   195  }
   196  
   197  func (s *Server) handleMultipartUpload(req *Request, boundary string, mw *api.ManifestWriter) error {
   198  	mr := multipart.NewReader(req.Body, boundary)
   199  	for {
   200  		part, err := mr.NextPart()
   201  		if err == io.EOF {
   202  			return nil
   203  		} else if err != nil {
   204  			return fmt.Errorf("error reading multipart form: %s", err)
   205  		}
   206  
   207  		var size int64
   208  		var reader io.Reader = part
   209  		if contentLength := part.Header.Get("Content-Length"); contentLength != "" {
   210  			size, err = strconv.ParseInt(contentLength, 10, 64)
   211  			if err != nil {
   212  				return fmt.Errorf("error parsing multipart content length: %s", err)
   213  			}
   214  			reader = part
   215  		} else {
   216  			// copy the part to a tmp file to get its size
   217  			tmp, err := ioutil.TempFile("", "swarm-multipart")
   218  			if err != nil {
   219  				return err
   220  			}
   221  			defer os.Remove(tmp.Name())
   222  			defer tmp.Close()
   223  			size, err = io.Copy(tmp, part)
   224  			if err != nil {
   225  				return fmt.Errorf("error copying multipart content: %s", err)
   226  			}
   227  			if _, err := tmp.Seek(0, os.SEEK_SET); err != nil {
   228  				return fmt.Errorf("error copying multipart content: %s", err)
   229  			}
   230  			reader = tmp
   231  		}
   232  
   233  		// add the entry under the path from the request
   234  		name := part.FileName()
   235  		if name == "" {
   236  			name = part.FormName()
   237  		}
   238  		path := path.Join(req.uri.Path, name)
   239  		entry := &api.ManifestEntry{
   240  			Path:        path,
   241  			ContentType: part.Header.Get("Content-Type"),
   242  			Size:        size,
   243  			ModTime:     time.Now(),
   244  		}
   245  		s.logDebug("adding %s (%d bytes) to new manifest", entry.Path, entry.Size)
   246  		contentKey, err := mw.AddEntry(reader, entry)
   247  		if err != nil {
   248  			return fmt.Errorf("error adding manifest entry from multipart form: %s", err)
   249  		}
   250  		s.logDebug("content for %s stored", contentKey.Log())
   251  	}
   252  }
   253  
   254  func (s *Server) handleDirectUpload(req *Request, mw *api.ManifestWriter) error {
   255  	key, err := mw.AddEntry(req.Body, &api.ManifestEntry{
   256  		Path:        req.uri.Path,
   257  		ContentType: req.Header.Get("Content-Type"),
   258  		Mode:        0644,
   259  		Size:        req.ContentLength,
   260  		ModTime:     time.Now(),
   261  	})
   262  	if err != nil {
   263  		return err
   264  	}
   265  	s.logDebug("content for %s stored", key.Log())
   266  	return nil
   267  }
   268  
   269  // HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes
   270  // <path> from <manifest> and returns the resulting manifest hash as a
   271  // text/plain response
   272  func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) {
   273  	key, err := s.api.Resolve(r.uri)
   274  	if err != nil {
   275  		s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
   276  		return
   277  	}
   278  
   279  	newKey, err := s.updateManifest(key, func(mw *api.ManifestWriter) error {
   280  		s.logDebug("removing %s from manifest %s", r.uri.Path, key.Log())
   281  		return mw.RemoveEntry(r.uri.Path)
   282  	})
   283  	if err != nil {
   284  		s.Error(w, r, fmt.Errorf("error updating manifest: %s", err))
   285  		return
   286  	}
   287  
   288  	w.Header().Set("Content-Type", "text/plain")
   289  	w.WriteHeader(http.StatusOK)
   290  	fmt.Fprint(w, newKey)
   291  }
   292  
   293  // HandleGetRaw handles a GET request to bzzr://<key> and responds with
   294  // the raw content stored at the given storage key
   295  func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) {
   296  	key, err := s.api.Resolve(r.uri)
   297  	if err != nil {
   298  		s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
   299  		return
   300  	}
   301  
   302  	// if path is set, interpret <key> as a manifest and return the
   303  	// raw entry at the given path
   304  	if r.uri.Path != "" {
   305  		walker, err := s.api.NewManifestWalker(key, nil)
   306  		if err != nil {
   307  			s.BadRequest(w, r, fmt.Sprintf("%s is not a manifest", key))
   308  			return
   309  		}
   310  		var entry *api.ManifestEntry
   311  		walker.Walk(func(e *api.ManifestEntry) error {
   312  			// if the entry matches the path, set entry and stop
   313  			// the walk
   314  			if e.Path == r.uri.Path {
   315  				entry = e
   316  				// return an error to cancel the walk
   317  				return errors.New("found")
   318  			}
   319  
   320  			// ignore non-manifest files
   321  			if e.ContentType != api.ManifestType {
   322  				return nil
   323  			}
   324  
   325  			// if the manifest's path is a prefix of the
   326  			// requested path, recurse into it by returning
   327  			// nil and continuing the walk
   328  			if strings.HasPrefix(r.uri.Path, e.Path) {
   329  				return nil
   330  			}
   331  
   332  			return api.SkipManifest
   333  		})
   334  		if entry == nil {
   335  			http.NotFound(w, &r.Request)
   336  			return
   337  		}
   338  		key = storage.Key(common.Hex2Bytes(entry.Hash))
   339  	}
   340  
   341  	// check the root chunk exists by retrieving the file's size
   342  	reader := s.api.Retrieve(key)
   343  	if _, err := reader.Size(nil); err != nil {
   344  		s.logDebug("key not found %s: %s", key, err)
   345  		http.NotFound(w, &r.Request)
   346  		return
   347  	}
   348  
   349  	// allow the request to overwrite the content type using a query
   350  	// parameter
   351  	contentType := "application/octet-stream"
   352  	if typ := r.URL.Query().Get("content_type"); typ != "" {
   353  		contentType = typ
   354  	}
   355  	w.Header().Set("Content-Type", contentType)
   356  
   357  	http.ServeContent(w, &r.Request, "", time.Now(), reader)
   358  }
   359  
   360  // HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept
   361  // header of "application/x-tar" and returns a tar stream of all files
   362  // contained in the manifest
   363  func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) {
   364  	if r.uri.Path != "" {
   365  		s.BadRequest(w, r, "files request cannot contain a path")
   366  		return
   367  	}
   368  
   369  	key, err := s.api.Resolve(r.uri)
   370  	if err != nil {
   371  		s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
   372  		return
   373  	}
   374  
   375  	walker, err := s.api.NewManifestWalker(key, nil)
   376  	if err != nil {
   377  		s.Error(w, r, err)
   378  		return
   379  	}
   380  
   381  	tw := tar.NewWriter(w)
   382  	defer tw.Close()
   383  	w.Header().Set("Content-Type", "application/x-tar")
   384  	w.WriteHeader(http.StatusOK)
   385  
   386  	err = walker.Walk(func(entry *api.ManifestEntry) error {
   387  		// ignore manifests (walk will recurse into them)
   388  		if entry.ContentType == api.ManifestType {
   389  			return nil
   390  		}
   391  
   392  		// retrieve the entry's key and size
   393  		reader := s.api.Retrieve(storage.Key(common.Hex2Bytes(entry.Hash)))
   394  		size, err := reader.Size(nil)
   395  		if err != nil {
   396  			return err
   397  		}
   398  
   399  		// write a tar header for the entry
   400  		hdr := &tar.Header{
   401  			Name:    entry.Path,
   402  			Mode:    entry.Mode,
   403  			Size:    size,
   404  			ModTime: entry.ModTime,
   405  			Xattrs: map[string]string{
   406  				"user.swarm.content-type": entry.ContentType,
   407  			},
   408  		}
   409  		if err := tw.WriteHeader(hdr); err != nil {
   410  			return err
   411  		}
   412  
   413  		// copy the file into the tar stream
   414  		n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size))
   415  		if err != nil {
   416  			return err
   417  		} else if n != size {
   418  			return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n)
   419  		}
   420  
   421  		return nil
   422  	})
   423  	if err != nil {
   424  		s.logError("error generating tar stream: %s", err)
   425  	}
   426  }
   427  
   428  // HandleGetList handles a GET request to bzz:/<manifest>/<path> which has
   429  // the "list" query parameter set to "true" and returns a list of all files
   430  // contained in <manifest> under <path> grouped into common prefixes using
   431  // "/" as a delimiter
   432  func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
   433  	// ensure the root path has a trailing slash so that relative URLs work
   434  	if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") {
   435  		http.Redirect(w, &r.Request, r.URL.Path+"/?list=true", http.StatusMovedPermanently)
   436  		return
   437  	}
   438  
   439  	key, err := s.api.Resolve(r.uri)
   440  	if err != nil {
   441  		s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
   442  		return
   443  	}
   444  
   445  	walker, err := s.api.NewManifestWalker(key, nil)
   446  	if err != nil {
   447  		s.Error(w, r, err)
   448  		return
   449  	}
   450  
   451  	var list api.ManifestList
   452  	prefix := r.uri.Path
   453  	err = walker.Walk(func(entry *api.ManifestEntry) error {
   454  		// handle non-manifest files
   455  		if entry.ContentType != api.ManifestType {
   456  			// ignore the file if it doesn't have the specified prefix
   457  			if !strings.HasPrefix(entry.Path, prefix) {
   458  				return nil
   459  			}
   460  
   461  			// if the path after the prefix contains a slash, add a
   462  			// common prefix to the list, otherwise add the entry
   463  			suffix := strings.TrimPrefix(entry.Path, prefix)
   464  			if index := strings.Index(suffix, "/"); index > -1 {
   465  				list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1])
   466  				return nil
   467  			}
   468  			if entry.Path == "" {
   469  				entry.Path = "/"
   470  			}
   471  			list.Entries = append(list.Entries, entry)
   472  			return nil
   473  		}
   474  
   475  		// if the manifest's path is a prefix of the specified prefix
   476  		// then just recurse into the manifest by returning nil and
   477  		// continuing the walk
   478  		if strings.HasPrefix(prefix, entry.Path) {
   479  			return nil
   480  		}
   481  
   482  		// if the manifest's path has the specified prefix, then if the
   483  		// path after the prefix contains a slash, add a common prefix
   484  		// to the list and skip the manifest, otherwise recurse into
   485  		// the manifest by returning nil and continuing the walk
   486  		if strings.HasPrefix(entry.Path, prefix) {
   487  			suffix := strings.TrimPrefix(entry.Path, prefix)
   488  			if index := strings.Index(suffix, "/"); index > -1 {
   489  				list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1])
   490  				return api.SkipManifest
   491  			}
   492  			return nil
   493  		}
   494  
   495  		// the manifest neither has the prefix or needs recursing in to
   496  		// so just skip it
   497  		return api.SkipManifest
   498  	})
   499  	if err != nil {
   500  		s.Error(w, r, err)
   501  		return
   502  	}
   503  
   504  	// if the client wants HTML (e.g. a browser) then render the list as a
   505  	// HTML index with relative URLs
   506  	if strings.Contains(r.Header.Get("Accept"), "text/html") {
   507  		w.Header().Set("Content-Type", "text/html")
   508  		err := htmlListTemplate.Execute(w, &htmlListData{
   509  			URI:  r.uri,
   510  			List: &list,
   511  		})
   512  		if err != nil {
   513  			s.logError("error rendering list HTML: %s", err)
   514  		}
   515  		return
   516  	}
   517  
   518  	w.Header().Set("Content-Type", "application/json")
   519  	json.NewEncoder(w).Encode(&list)
   520  }
   521  
   522  // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds
   523  // with the content of the file at <path> from the given <manifest>
   524  func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
   525  	key, err := s.api.Resolve(r.uri)
   526  	if err != nil {
   527  		s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
   528  		return
   529  	}
   530  
   531  	reader, contentType, _, err := s.api.Get(key, r.uri.Path)
   532  	if err != nil {
   533  		s.Error(w, r, err)
   534  		return
   535  	}
   536  
   537  	// check the root chunk exists by retrieving the file's size
   538  	if _, err := reader.Size(nil); err != nil {
   539  		s.logDebug("file not found %s: %s", r.uri, err)
   540  		http.NotFound(w, &r.Request)
   541  		return
   542  	}
   543  
   544  	w.Header().Set("Content-Type", contentType)
   545  
   546  	http.ServeContent(w, &r.Request, "", time.Now(), reader)
   547  }
   548  
   549  func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   550  	s.logDebug("HTTP %s request URL: '%s', Host: '%s', Path: '%s', Referer: '%s', Accept: '%s'", r.Method, r.RequestURI, r.URL.Host, r.URL.Path, r.Referer(), r.Header.Get("Accept"))
   551  
   552  	uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/"))
   553  	if err != nil {
   554  		s.logError("Invalid URI %q: %s", r.URL.Path, err)
   555  		http.Error(w, fmt.Sprintf("Invalid bzz URI: %s", err), http.StatusBadRequest)
   556  		return
   557  	}
   558  	s.logDebug("%s request received for %s", r.Method, uri)
   559  
   560  	req := &Request{Request: *r, uri: uri}
   561  	switch r.Method {
   562  	case "POST":
   563  		if uri.Raw() {
   564  			s.HandlePostRaw(w, req)
   565  		} else {
   566  			s.HandlePostFiles(w, req)
   567  		}
   568  
   569  	case "PUT":
   570  		// DEPRECATED:
   571  		//   clients should send a POST request (the request creates a
   572  		//   new manifest leaving the existing one intact, so it isn't
   573  		//   strictly a traditional PUT request which replaces content
   574  		//   at a URI, and POST is more ubiquitous)
   575  		if uri.Raw() {
   576  			http.Error(w, fmt.Sprintf("No PUT to %s allowed.", uri), http.StatusBadRequest)
   577  			return
   578  		} else {
   579  			s.HandlePostFiles(w, req)
   580  		}
   581  
   582  	case "DELETE":
   583  		if uri.Raw() {
   584  			http.Error(w, fmt.Sprintf("No DELETE to %s allowed.", uri), http.StatusBadRequest)
   585  			return
   586  		}
   587  		s.HandleDelete(w, req)
   588  
   589  	case "GET":
   590  		if uri.Raw() {
   591  			s.HandleGetRaw(w, req)
   592  			return
   593  		}
   594  
   595  		if r.Header.Get("Accept") == "application/x-tar" {
   596  			s.HandleGetFiles(w, req)
   597  			return
   598  		}
   599  
   600  		if r.URL.Query().Get("list") == "true" {
   601  			s.HandleGetList(w, req)
   602  			return
   603  		}
   604  
   605  		s.HandleGetFile(w, req)
   606  
   607  	default:
   608  		http.Error(w, "Method "+r.Method+" is not supported.", http.StatusMethodNotAllowed)
   609  
   610  	}
   611  }
   612  
   613  func (s *Server) updateManifest(key storage.Key, update func(mw *api.ManifestWriter) error) (storage.Key, error) {
   614  	mw, err := s.api.NewManifestWriter(key, nil)
   615  	if err != nil {
   616  		return nil, err
   617  	}
   618  
   619  	if err := update(mw); err != nil {
   620  		return nil, err
   621  	}
   622  
   623  	key, err = mw.Store()
   624  	if err != nil {
   625  		return nil, err
   626  	}
   627  	s.logDebug("generated manifest %s", key)
   628  	return key, nil
   629  }
   630  
   631  func (s *Server) logDebug(format string, v ...interface{}) {
   632  	log.Debug(fmt.Sprintf("[BZZ] HTTP: "+format, v...))
   633  }
   634  
   635  func (s *Server) logError(format string, v ...interface{}) {
   636  	log.Error(fmt.Sprintf("[BZZ] HTTP: "+format, v...))
   637  }
   638  
   639  func (s *Server) BadRequest(w http.ResponseWriter, r *Request, reason string) {
   640  	s.logDebug("bad request %s %s: %s", r.Method, r.uri, reason)
   641  	http.Error(w, reason, http.StatusBadRequest)
   642  }
   643  
   644  func (s *Server) Error(w http.ResponseWriter, r *Request, err error) {
   645  	s.logError("error serving %s %s: %s", r.Method, r.uri, err)
   646  	http.Error(w, err.Error(), http.StatusInternalServerError)
   647  }