Skip to content

Commit 2dd87ce

Browse files
Merge pull request #195 from MichaelEischer/internal-error-for-inaccessible-files
Return "internal server error" if files cannot be read
2 parents 9f4fba2 + f763db8 commit 2dd87ce

File tree

3 files changed

+182
-92
lines changed

3 files changed

+182
-92
lines changed

changelog/unreleased/pull-194

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Bugfix: Return "internal server error" if files cannot be read
2+
3+
When files in a repository cannot be read by rest-server, for example after
4+
running `restic prune` directly on the server hosting the repositories, then
5+
rest-server returned "Not Found" as status code. This is extremely confusing
6+
for users.
7+
8+
The error handling has been fixed to only return "Not Found" if the file
9+
actually does not exist. Otherwise an internal server error is reported to the
10+
user and the underlying error is logged at the server side.
11+
12+
https://github.com/restic/restic/issues/1871
13+
https://github.com/restic/rest-server/pull/194

handlers_test.go

Lines changed: 137 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http"
1111
"net/http/httptest"
1212
"os"
13+
"path"
1314
"path/filepath"
1415
"reflect"
1516
"strings"
@@ -164,8 +165,7 @@ func createOverwriteDeleteSeq(t testing.TB, path string, data string) []TestRequ
164165
return req
165166
}
166167

167-
// TestResticHandler runs tests on the restic handler code, especially in append-only mode.
168-
func TestResticHandler(t *testing.T) {
168+
func createTestHandler(t *testing.T, conf Server) (http.Handler, string, string, string, func()) {
169169
buf := make([]byte, 32)
170170
_, err := io.ReadFull(rand.Reader, buf)
171171
if err != nil {
@@ -175,6 +175,38 @@ func TestResticHandler(t *testing.T) {
175175
dataHash := sha256.Sum256([]byte(data))
176176
fileID := hex.EncodeToString(dataHash[:])
177177

178+
// setup the server with a local backend in a temporary directory
179+
tempdir, err := ioutil.TempDir("", "rest-server-test-")
180+
if err != nil {
181+
t.Fatal(err)
182+
}
183+
184+
// make sure the tempdir is properly removed
185+
cleanup := func() {
186+
err := os.RemoveAll(tempdir)
187+
if err != nil {
188+
t.Fatal(err)
189+
}
190+
}
191+
192+
conf.Path = tempdir
193+
mux, err := NewHandler(&conf)
194+
if err != nil {
195+
t.Fatalf("error from NewHandler: %v", err)
196+
}
197+
return mux, data, fileID, tempdir, cleanup
198+
}
199+
200+
// TestResticAppendOnlyHandler runs tests on the restic handler code, especially in append-only mode.
201+
func TestResticAppendOnlyHandler(t *testing.T) {
202+
mux, data, fileID, _, cleanup := createTestHandler(t, Server{
203+
AppendOnly: true,
204+
NoAuth: true,
205+
Debug: true,
206+
PanicOnError: true,
207+
})
208+
defer cleanup()
209+
178210
var tests = []struct {
179211
seq []TestRequest
180212
}{
@@ -226,37 +258,115 @@ func TestResticHandler(t *testing.T) {
226258
{createOverwriteDeleteSeq(t, "/parent2/data/"+fileID, data)},
227259
}
228260

229-
// setup the server with a local backend in a temporary directory
230-
tempdir, err := ioutil.TempDir("", "rest-server-test-")
231-
if err != nil {
232-
t.Fatal(err)
261+
// create the repos
262+
for _, path := range []string{"/", "/parent1/sub1/", "/parent1/", "/parent2/"} {
263+
checkRequest(t, mux.ServeHTTP,
264+
newRequest(t, "POST", path+"?create=true", nil),
265+
[]wantFunc{wantCode(http.StatusOK)})
233266
}
234267

235-
// make sure the tempdir is properly removed
236-
defer func() {
237-
err := os.RemoveAll(tempdir)
238-
if err != nil {
239-
t.Fatal(err)
240-
}
241-
}()
268+
for _, test := range tests {
269+
t.Run("", func(t *testing.T) {
270+
for i, seq := range test.seq {
271+
t.Logf("request %v: %v %v", i, seq.req.Method, seq.req.URL.Path)
272+
checkRequest(t, mux.ServeHTTP, seq.req, seq.want)
273+
}
274+
})
275+
}
276+
}
242277

243-
// set append-only mode and configure path
244-
mux, err := NewHandler(&Server{
245-
AppendOnly: true,
246-
Path: tempdir,
278+
// createOverwriteDeleteSeq returns a sequence which will create a new file at
279+
// path, and then deletes it twice.
280+
func createIdempotentDeleteSeq(t testing.TB, path string, data string) []TestRequest {
281+
return []TestRequest{
282+
{
283+
req: newRequest(t, "POST", path, strings.NewReader(data)),
284+
want: []wantFunc{wantCode(http.StatusOK)},
285+
},
286+
{
287+
req: newRequest(t, "DELETE", path, nil),
288+
want: []wantFunc{wantCode(http.StatusOK)},
289+
},
290+
{
291+
req: newRequest(t, "GET", path, nil),
292+
want: []wantFunc{wantCode(http.StatusNotFound)},
293+
},
294+
{
295+
req: newRequest(t, "DELETE", path, nil),
296+
want: []wantFunc{wantCode(http.StatusOK)},
297+
},
298+
}
299+
}
300+
301+
// TestResticHandler runs tests on the restic handler code, especially in append-only mode.
302+
func TestResticHandler(t *testing.T) {
303+
mux, data, fileID, _, cleanup := createTestHandler(t, Server{
247304
NoAuth: true,
248305
Debug: true,
249306
PanicOnError: true,
250307
})
251-
if err != nil {
252-
t.Fatalf("error from NewHandler: %v", err)
308+
defer cleanup()
309+
310+
var tests = []struct {
311+
seq []TestRequest
312+
}{
313+
{createIdempotentDeleteSeq(t, "/config", data)},
314+
{createIdempotentDeleteSeq(t, "/data/"+fileID, data)},
253315
}
254316

255-
// create the repos
256-
for _, path := range []string{"/", "/parent1/sub1/", "/parent1/", "/parent2/"} {
257-
checkRequest(t, mux.ServeHTTP,
258-
newRequest(t, "POST", path+"?create=true", nil),
259-
[]wantFunc{wantCode(http.StatusOK)})
317+
// create the repo
318+
checkRequest(t, mux.ServeHTTP,
319+
newRequest(t, "POST", "/?create=true", nil),
320+
[]wantFunc{wantCode(http.StatusOK)})
321+
322+
for _, test := range tests {
323+
t.Run("", func(t *testing.T) {
324+
for i, seq := range test.seq {
325+
t.Logf("request %v: %v %v", i, seq.req.Method, seq.req.URL.Path)
326+
checkRequest(t, mux.ServeHTTP, seq.req, seq.want)
327+
}
328+
})
329+
}
330+
}
331+
332+
// TestResticErrorHandler runs tests on the restic handler error handling.
333+
func TestResticErrorHandler(t *testing.T) {
334+
mux, _, _, tempdir, cleanup := createTestHandler(t, Server{
335+
AppendOnly: true,
336+
NoAuth: true,
337+
Debug: true,
338+
})
339+
defer cleanup()
340+
341+
var tests = []struct {
342+
seq []TestRequest
343+
}{
344+
// Test inaccessible file
345+
{
346+
[]TestRequest{{
347+
req: newRequest(t, "GET", "/config", nil),
348+
want: []wantFunc{wantCode(http.StatusInternalServerError)},
349+
}},
350+
},
351+
{
352+
[]TestRequest{{
353+
req: newRequest(t, "GET", "/parent4/config", nil),
354+
want: []wantFunc{wantCode(http.StatusNotFound)},
355+
}},
356+
},
357+
}
358+
359+
// create the repo
360+
checkRequest(t, mux.ServeHTTP,
361+
newRequest(t, "POST", "/?create=true", nil),
362+
[]wantFunc{wantCode(http.StatusOK)})
363+
// create inaccessible config
364+
checkRequest(t, mux.ServeHTTP,
365+
newRequest(t, "POST", "/config", strings.NewReader("example")),
366+
[]wantFunc{wantCode(http.StatusOK)})
367+
err := os.Chmod(path.Join(tempdir, "config"), 0o000)
368+
if err != nil {
369+
t.Fatal(err)
260370
}
261371

262372
for _, test := range tests {
@@ -359,31 +469,13 @@ func (d *delayErrorReader) Read(p []byte) (int, error) {
359469

360470
// TestAbortedRequest runs tests with concurrent upload requests for the same file.
361471
func TestAbortedRequest(t *testing.T) {
362-
// setup the server with a local backend in a temporary directory
363-
tempdir, err := ioutil.TempDir("", "rest-server-test-")
364-
if err != nil {
365-
t.Fatal(err)
366-
}
367-
368-
// make sure the tempdir is properly removed
369-
defer func() {
370-
err := os.RemoveAll(tempdir)
371-
if err != nil {
372-
t.Fatal(err)
373-
}
374-
}()
375-
376-
// configure path, the race condition doesn't happen for append-only repositories
377-
mux, err := NewHandler(&Server{
378-
AppendOnly: false,
379-
Path: tempdir,
472+
// the race condition doesn't happen for append-only repositories
473+
mux, _, _, _, cleanup := createTestHandler(t, Server{
380474
NoAuth: true,
381475
Debug: true,
382476
PanicOnError: true,
383477
})
384-
if err != nil {
385-
t.Fatalf("error from NewHandler: %v", err)
386-
}
478+
defer cleanup()
387479

388480
// create the repo
389481
checkRequest(t, mux.ServeHTTP,

0 commit comments

Comments
 (0)