Skip to content

Commit 19eeca3

Browse files
adelowojeevatkm
authored andcommitted
Added convenient methods for uploading files. (#15) go-aah/aah#82
1 parent 9ab8efd commit 19eeca3

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed

request.go

+61
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
package ahttp
66

77
import (
8+
"errors"
89
"fmt"
10+
"io"
911
"mime/multipart"
1012
"net"
1113
"net/http"
1214
"net/url"
15+
"os"
16+
"path/filepath"
1317
"strings"
1418
"sync"
1519

@@ -196,6 +200,52 @@ func (r *Request) Unwrap() *http.Request {
196200
return r.Raw
197201
}
198202

203+
// SaveFile method saves an uploaded multipart file for given key from the HTTP request into given destination
204+
func (r *Request) SaveFile(key, dstFile string) error {
205+
if ess.IsStrEmpty(dstFile) || ess.IsStrEmpty(key) {
206+
return errors.New("ahttp: key or dstFile is empty")
207+
}
208+
209+
if ess.IsDir(dstFile) {
210+
return errors.New("ahttp: dstFile should not be a directory")
211+
}
212+
213+
uploadedFile, _, err := r.FormFile(key)
214+
if err != nil {
215+
return err
216+
}
217+
defer ess.CloseQuietly(uploadedFile)
218+
219+
return saveFile(uploadedFile, dstFile)
220+
}
221+
222+
// SaveFiles method saves an uploaded multipart file(s) for the given key from the HTTP request into given destination directory.
223+
// It uses the filename as uploaded filename from the request
224+
func (r *Request) SaveFiles(key, dstPath string) []error {
225+
if !ess.IsDir(dstPath) {
226+
return []error{fmt.Errorf("ahttp: destination path, %s is not a directory", dstPath)}
227+
}
228+
229+
if ess.IsStrEmpty(key) {
230+
return []error{fmt.Errorf("ahttp: form file key, %s is empty.", key)}
231+
}
232+
233+
var errs []error
234+
for _, file := range r.Params.File[key] {
235+
uploadedFile, err := file.Open()
236+
if err != nil {
237+
errs = append(errs, err)
238+
continue
239+
}
240+
241+
if err := saveFile(uploadedFile, filepath.Join(dstPath, file.Filename)); err != nil {
242+
errs = append(errs, err)
243+
}
244+
ess.CloseQuietly(uploadedFile)
245+
}
246+
return errs
247+
}
248+
199249
// Reset method resets request instance for reuse.
200250
func (r *Request) Reset() {
201251
r.Scheme = ""
@@ -356,3 +406,14 @@ func isGzipAccepted(req *Request, r *http.Request) bool {
356406
}
357407
return false
358408
}
409+
410+
func saveFile(r io.Reader, destFile string) error {
411+
f, err := os.OpenFile(destFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
412+
if err != nil {
413+
return err
414+
}
415+
defer ess.CloseQuietly(f)
416+
417+
_, err = io.Copy(f, r)
418+
return err
419+
}

request_test.go

+123
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
package ahttp
66

77
import (
8+
"bytes"
89
"crypto/tls"
910
"mime/multipart"
1011
"net/http"
1112
"net/http/httptest"
1213
"net/url"
14+
"os"
1315
"strings"
1416
"testing"
1517

@@ -193,3 +195,124 @@ func TestRequestSchemeDerived(t *testing.T) {
193195
func createRequestWithHost(host, remote string) *http.Request {
194196
return &http.Request{Host: host, RemoteAddr: remote, Header: http.Header{}}
195197
}
198+
199+
func setUpRequestSaveFile(t *testing.T) (*Request, string, func()) {
200+
buf := new(bytes.Buffer)
201+
multipartWriter := multipart.NewWriter(buf)
202+
_, err := multipartWriter.CreateFormFile("framework", "aah")
203+
assert.Nil(t, err)
204+
205+
multipartWriter.Close()
206+
207+
req, _ := http.NewRequest("POST", "http://localhost:8080", buf)
208+
req.Header.Add(HeaderContentType, multipartWriter.FormDataContentType())
209+
aahReq := AcquireRequest(req)
210+
aahReq.Params.File = make(map[string][]*multipart.FileHeader)
211+
212+
_, header, err := req.FormFile("framework")
213+
assert.Nil(t, err)
214+
215+
aahReq.Params.File["framework"] = []*multipart.FileHeader{header}
216+
217+
path := "testdata/aah.txt"
218+
219+
return aahReq, path, func() {
220+
os.Remove(path) //Teardown
221+
}
222+
}
223+
224+
func TestRequestSaveFile(t *testing.T) {
225+
aahReq, path, teardown := setUpRequestSaveFile(t)
226+
defer teardown()
227+
228+
assert.Nil(t, aahReq.SaveFile("framework", path))
229+
_, err := os.Stat(path)
230+
assert.Nil(t, err)
231+
}
232+
233+
func TestRequestSaveFileFailsValidation(t *testing.T) {
234+
aahReq, path, teardown := setUpRequestSaveFile(t)
235+
defer teardown()
236+
237+
// Empty keys should error out
238+
assert.NotNil(t, aahReq.SaveFile("", path))
239+
240+
// Empty path should error out
241+
assert.NotNil(t, aahReq.SaveFile("framework", ""))
242+
243+
// If "path" is a directory, it should error out
244+
assert.NotNil(t, aahReq.SaveFile("framework", "testdata"))
245+
}
246+
247+
func TestRequestSaveFileFailsForNotFoundFile(t *testing.T) {
248+
aahReq, path, teardown := setUpRequestSaveFile(t)
249+
defer teardown()
250+
251+
assert.NotNil(t, aahReq.SaveFile("unknown-key", path))
252+
}
253+
254+
func TestRequestSaveFileCannotCreateFile(t *testing.T) {
255+
aahReq, _, teardown := setUpRequestSaveFile(t)
256+
defer teardown()
257+
258+
assert.NotNil(t, aahReq.SaveFile("framework", "/root/aah.txt"))
259+
}
260+
261+
func setUpRequestSaveFiles(t *testing.T) (*Request, string, func()) {
262+
buf := new(bytes.Buffer)
263+
multipartWriter := multipart.NewWriter(buf)
264+
_, err := multipartWriter.CreateFormFile("framework", "aah")
265+
assert.Nil(t, err)
266+
_, err = multipartWriter.CreateFormFile("framework2", "aah2")
267+
assert.Nil(t, err)
268+
269+
multipartWriter.Close()
270+
271+
req, _ := http.NewRequest("POST", "http://localhost:8080", buf)
272+
req.Header.Add(HeaderContentType, multipartWriter.FormDataContentType())
273+
aahReq := AcquireRequest(req)
274+
aahReq.Params.File = make(map[string][]*multipart.FileHeader)
275+
276+
_, header, err := req.FormFile("framework")
277+
assert.Nil(t, err)
278+
_, header2, err := req.FormFile("framework2")
279+
assert.Nil(t, err)
280+
281+
aahReq.Params.File["framework"] = []*multipart.FileHeader{header, header2}
282+
283+
dir := "testdata/upload"
284+
285+
os.Mkdir(dir, 0755)
286+
return aahReq, dir, func() {
287+
os.RemoveAll(dir)
288+
}
289+
}
290+
291+
func TestRequestSaveFiles(t *testing.T) {
292+
aahReq, dir, teardown := setUpRequestSaveFiles(t)
293+
defer teardown()
294+
295+
assert.Nil(t, aahReq.SaveFiles("framework", dir))
296+
_, err := os.Stat(dir + "/aah")
297+
assert.Nil(t, err)
298+
_, err = os.Stat(dir + "/aah2")
299+
assert.Nil(t, err)
300+
}
301+
302+
func TestRequestSaveFilesFailsVaildation(t *testing.T) {
303+
aahReq, dir, teardown := setUpRequestSaveFiles(t)
304+
defer teardown()
305+
306+
// Empty key
307+
assert.NotNil(t, aahReq.SaveFiles("", dir))
308+
309+
// Empty directory
310+
assert.NotNil(t, aahReq.SaveFiles("key", ""))
311+
}
312+
313+
func TestRequestSaveFilesCannotCreateFile(t *testing.T) {
314+
aahReq, _, teardown := setUpRequestSaveFiles(t)
315+
defer teardown()
316+
317+
assert.NotNil(t, aahReq.SaveFiles("framework", "/root"))
318+
}

0 commit comments

Comments
 (0)