Skip to content

Commit 8bab785

Browse files
committed
add go example
1 parent da5073d commit 8bab785

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

samples/Backend on Go.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Backend in Go
2+
3+
Each `POST` request is parsed and then saved to disk. After the final chunk is uploaded, the chunks are stitched together in a separate go routine and then deleted.
4+
5+
This implementation assumes that the final chunk is the last piece of the file being uploaded.
6+
7+
Full working code available at https://github.com/stuartnelson3/golang-flowjs-upload
8+
9+
The above repo includes an additional handler that streams the `POST` request chunks to disk, lowering the overall memory footprint.
10+
11+
```go
12+
package main
13+
14+
import (
15+
"bytes"
16+
"github.com/codegangsta/martini"
17+
"github.com/codegangsta/martini-contrib/render"
18+
"io"
19+
"io/ioutil"
20+
"net/http"
21+
"os"
22+
"sort"
23+
"strconv"
24+
"strings"
25+
)
26+
27+
var completedFiles = make(chan string, 100)
28+
29+
func main() {
30+
for i := 0; i < 3; i++ {
31+
go assembleFile(completedFiles)
32+
}
33+
34+
m := martini.Classic()
35+
m.Use(render.Renderer(render.Options{
36+
Layout: "layout",
37+
Delims: render.Delims{"{[{", "}]}"},
38+
Extensions: []string{".html"}}))
39+
40+
m.Get("/", func(r render.Render) {
41+
r.HTML(200, "index", nil)
42+
})
43+
44+
m.Post("/upload", streamHandler(chunkedReader))
45+
46+
m.Run()
47+
}
48+
49+
type ByChunk []os.FileInfo
50+
51+
func (a ByChunk) Len() int { return len(a) }
52+
func (a ByChunk) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
53+
func (a ByChunk) Less(i, j int) bool {
54+
ai, _ := strconv.Atoi(a[i].Name())
55+
aj, _ := strconv.Atoi(a[j].Name())
56+
return ai < aj
57+
}
58+
59+
type streamHandler func(http.ResponseWriter, *http.Request) error
60+
61+
func (fn streamHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
62+
if err := fn(w, r); err != nil {
63+
http.Error(w, err.Error(), 500)
64+
}
65+
}
66+
67+
func chunkedReader(w http.ResponseWriter, r *http.Request) error {
68+
r.ParseMultipartForm(25)
69+
70+
chunkDirPath := "./incomplete/" + r.FormValue("flowFilename")
71+
_, err := os.Stat(chunkDirPath)
72+
if err != nil {
73+
err := os.MkdirAll(chunkDirPath, 02750)
74+
if err != nil {
75+
return err
76+
}
77+
}
78+
79+
for _, fileHeader := range r.MultipartForm.File["file"] {
80+
src, err := fileHeader.Open()
81+
if err != nil {
82+
return err
83+
}
84+
defer src.Close()
85+
86+
dst, err := os.Create(chunkDirPath + "/" + r.FormValue("flowChunkNumber"))
87+
if err != nil {
88+
return err
89+
}
90+
defer dst.Close()
91+
io.Copy(dst, src)
92+
93+
if r.FormValue("flowChunkNumber") == r.FormValue("flowTotalChunks") {
94+
completedFiles <- chunkDirPath
95+
}
96+
}
97+
return nil
98+
}
99+
100+
func assembleFile(jobs <-chan string) {
101+
for path := range jobs {
102+
fileInfos, err := ioutil.ReadDir(path)
103+
if err != nil {
104+
return
105+
}
106+
107+
// create final file to write to
108+
dst, err := os.Create(strings.Split(path, "/")[2])
109+
if err != nil {
110+
return
111+
}
112+
defer dst.Close()
113+
114+
sort.Sort(ByChunk(fileInfos))
115+
for _, fs := range fileInfos {
116+
src, err := os.Open(path + "/" + fs.Name())
117+
if err != nil {
118+
return
119+
}
120+
defer src.Close()
121+
io.Copy(dst, src)
122+
}
123+
os.RemoveAll(path)
124+
}
125+
}
126+
```

0 commit comments

Comments
 (0)