Skip to content

Commit acde289

Browse files
committed
Merge pull request #26 from stuartnelson3/master
feat(go): go sample
2 parents da5073d + 68a1727 commit acde289

1 file changed

Lines changed: 138 additions & 0 deletions

File tree

samples/Backend on Go.md

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

0 commit comments

Comments
 (0)