Repository: buchgr/bazel-remote (go)

Issue:
Internal Server Error when object not found in `http_proxy`
With `bazel-remote` running on localhost and an `http_proxy` configured to a remote `bazel-remote`, when an object is looked up remotely and not found, the local `bazel-remote` responds with a 500 Internal Server error.

This is the output from Bazel (0.22.0):
```
 $ bazel test //...
INFO: Using Bazel Remote listening on port 55381
INFO: Running Bazel version 0.22.0.4
INFO: Invocation ID: 243c2863-f4f3-4a27-b35c-8ee3362b4990
INFO: Analysed 40 targets (0 packages loaded, 0 targets configured).
INFO: Found 38 targets and 2 test targets...
WARNING: Error reading from the remote cache:
500 Internal Server Error
Not found

INFO: Elapsed time: 0.886s, Critical Path: 0.63s
INFO: 1 process: 1 darwin-sandbox.
INFO: Build completed successfully, 2 total actions
```

There are no obvious errors in the local `bazel-remote` log, but I used `tcpdump` to see the packets:

This is Bazel:
```
GET /ac/5a776703f6b95900eba8e7a1779df127f0c61a60610e9e1322c94d3bbe3bd1fd HTTP/1.1
host: 127.0.0.1:55381
connection: keep-alive
accept: */*
```

This is the local `bazel-remote` log:
```
2019/03/01 19:27:46  GET 404                 http://bazel-remote.example.com:8415/ac/5a776703f6b95900eba8e7a1779df127f0c61a60610e9e1322c94d3bbe3bd1fd
2019/03/01 19:27:46 GET /ac/5a776703f6b95900eba8e7a1779df127f0c61a60610e9e1322c94d3bbe3bd1fd: Not found
```

This is the response from the local `bazel-remote`:

```
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Fri, 01 Mar 2019 19:27:46 GMT
Content-Length: 11

Not found
```

The local `bazel-remote` should reply with an HTTP code 404 Not Found instead of a 500 Internal Server Error.


Pull request:
Fix misleading internal server error
If the http_proxy returned 404, bazel-remote was returning 500.

The test is a bit convoluted... I'm open to ideas on how to make it simpler. 

Fixes #84 

Patch:
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index 48aef5c..4a6248d 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -1,10 +1,5 @@
 ---
 platforms:
-  ubuntu1404:
-    build_targets:
-    - "..."
-    test_targets:
-    - "..."
   ubuntu1604:
     build_targets:
     - "..."
diff --git a/BUILD.bazel b/BUILD.bazel
index d9285f0..c812b75 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -39,6 +39,8 @@ go_image(
     embed = [":go_default_library"],
     pure = "on",
     static = "on",
+    goos = "linux",
+    goarch = "amd64",
     visibility = ["//visibility:private"],
 )
 
diff --git a/WORKSPACE b/WORKSPACE
index 6f28424..635ab56 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,22 +3,28 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 
 http_archive(
     name = "io_bazel_rules_go",
-    url = "https://github.com/bazelbuild/rules_go/releases/download/0.15.8/rules_go-0.15.8.tar.gz",
-    sha256 = "ca79fed5b24dcc0696e1651ecdd916f7a11111283ba46ea07633a53d8e1f5199",
+    url = "https://github.com/bazelbuild/rules_go/releases/download/0.18.4/rules_go-0.18.4.tar.gz",
+    sha256 = "3743a20704efc319070957c45e24ae4626a05ba4b1d6a8961e87520296f1b676",
 )
 
 http_archive(
     name = "bazel_gazelle",
-    url = "https://github.com/bazelbuild/bazel-gazelle/releases/download/0.15.0/bazel-gazelle-0.15.0.tar.gz",
-    sha256 = "6e875ab4b6bf64a38c352887760f21203ab054676d9c1b274963907e0768740d",
+    url = "https://github.com/bazelbuild/bazel-gazelle/releases/download/0.17.0/bazel-gazelle-0.17.0.tar.gz",
+    sha256 = "3c681998538231a2d24d0c07ed5a7658cb72bfb5fd4bf9911157c0e9ac6a2687",
 )
 
 git_repository(
     name = "io_bazel_rules_docker",
     remote = "https://github.com/bazelbuild/rules_docker.git",
-    tag = "v0.5.1",
+    tag = "v0.7.0",
 )
 
+load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
+
+go_rules_dependencies()
+
+go_register_toolchains()
+
 load(
     "@io_bazel_rules_docker//go:image.bzl",
     _go_image_repos = "repositories",
@@ -26,12 +32,6 @@ load(
 
 _go_image_repos()
 
-load("@io_bazel_rules_go//go:def.bzl", "go_register_toolchains", "go_rules_dependencies")
-
-go_rules_dependencies()
-
-go_register_toolchains()
-
 load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
 load("@bazel_gazelle//:def.bzl", "go_repository")
 
diff --git a/server/http.go b/server/http.go
index 713b8f4..6b27d6d 100644
--- a/server/http.go
+++ b/server/http.go
@@ -107,7 +107,11 @@ func (h *httpCache) CacheHandler(w http.ResponseWriter, r *http.Request) {
 	case http.MethodGet:
 		data, sizeBytes, err := h.cache.Get(kind, hash)
 		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
+			if e, ok := err.(*cache.Error); ok {
+				http.Error(w, e.Error(), e.Code)
+			} else {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+			}
 			h.errorLogger.Printf("GET %s: %s", path(kind, hash), err)
 			return
 		}


Test patch:
diff --git a/server/http_test.go b/server/http_test.go
index 98151cb..612e5ee 100644
--- a/server/http_test.go
+++ b/server/http_test.go
@@ -5,10 +5,12 @@ import (
 	"crypto/sha256"
 	"encoding/hex"
 	"encoding/json"
+	"fmt"
 	"io"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"net/url"
 	"os"
 	"path/filepath"
 	"sync"
@@ -293,3 +295,76 @@ func TestParseRequestURL(t *testing.T) {
 		}
 	}
 }
+
+type fakeCache struct {
+}
+
+func (f *fakeCache) Put(kind cache.EntryKind, hash string, size int64, r io.Reader) error {
+	return nil
+}
+
+func (f *fakeCache) Get(kind cache.EntryKind, hash string) (data io.ReadCloser, sizeBytes int64, err error) {
+	return nil, -1, &cache.Error{
+		Code: http.StatusNotFound,
+		Text: fmt.Sprintf("Not found\n"),
+	}
+}
+
+func (f *fakeCache) Contains(kind cache.EntryKind, hash string) (ok bool) {
+	return false
+}
+
+func (f *fakeCache) MaxSize() int64 {
+	return 0
+}
+
+func (f *fakeCache) CurrentSize() int64 {
+	return 0
+}
+func (f *fakeCache) NumItems() int {
+	return 0
+}
+
+type fakeResponseWriter struct {
+	statusCode *int
+	response   string
+}
+
+func (r fakeResponseWriter) Header() http.Header {
+	return http.Header{}
+}
+
+func (r fakeResponseWriter) Write(data []byte) (int, error) {
+	r.response = string(data)
+	return 0, nil
+}
+
+func (r fakeResponseWriter) WriteHeader(statusCode int) {
+	*r.statusCode = statusCode
+}
+
+func TestRemoteReturnsNotFound(t *testing.T) {
+	fake := &fakeCache{}
+	h := NewHTTPCache(fake, testutils.NewSilentLogger(), testutils.NewSilentLogger())
+	// create a fake http.Request
+	_, hash := testutils.RandomDataAndHash(1024)
+	url, _ := url.Parse(fmt.Sprintf("http://localhost:8080/ac/%s", hash))
+	reader := bytes.NewReader([]byte{})
+	body := ioutil.NopCloser(reader)
+	req := &http.Request{
+		Method:     "GET",
+		URL:        url,
+		Proto:      "HTTP/1.1",
+		ProtoMajor: 1,
+		ProtoMinor: 1,
+		Body:       body,
+	}
+	statusCode := 0
+	respWriter := fakeResponseWriter{
+		statusCode: &statusCode,
+	}
+	h.CacheHandler(respWriter, req)
+	if statusCode != http.StatusNotFound {
+		t.Errorf("Wrong status code, expected %d, got %d", http.StatusNotFound, statusCode)
+	}
+}