-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
139 lines (118 loc) · 3.68 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// snippet-store is a server for storing GopherJS Playground snippets.
//
// It uses the same mapping scheme as the Go Playground, and when a snippet isn't found locally,
// it defers to fetching it from the Go Playground. This effectively augments our world of available
// snippets with that of the Go Playground.
//
// Newly shared snippets are stored locally in the specified folder and take precedence.
package main
import (
"context"
"flag"
"io"
"log"
"net/http"
"os"
"time"
"golang.org/x/net/webdav"
)
var (
storageDirFlag = flag.String("storage-dir", "", "Storage dir for snippets; if empty, a volatile in-memory store is used.")
httpFlag = flag.String("http", ":8080", "Listen for HTTP connections on this address.")
allowOriginFlag = flag.String("allow-origin", "http://www.gopherjs.org", "Access-Control-Allow-Origin header value.")
)
const maxSnippetSizeBytes = 1024 * 1024
func main() {
flag.Parse()
var localStore webdav.FileSystem
switch *storageDirFlag {
default:
if fi, err := os.Stat(*storageDirFlag); os.IsNotExist(err) {
log.Fatalf("storage directory %q doesn't exist: %v", *storageDirFlag, err)
} else if err != nil {
log.Fatalf("error doing stat of directory %q: %v", *storageDirFlag, err)
} else if !fi.IsDir() {
log.Fatalf("file %q is not a directory", *storageDirFlag)
}
localStore = webdav.Dir(*storageDirFlag)
case "":
localStore = webdav.NewMemFS()
}
s := &Server{
Store: &Store{
LocalFS: localStore,
},
}
http.HandleFunc("/share", s.ShareHandler) // "/share", save snippet and return its id.
http.HandleFunc("/p/", s.PHandler) // "/p/{{.SnippetId}}", serve snippet by id.
log.Println("Started.")
err := http.ListenAndServe(*httpFlag, nil)
if err != nil {
log.Fatalln("ListenAndServe:", err)
}
}
// Server is the snippet store HTTP server.
type Server struct {
Store *Store
}
// ShareHandler is the handler for "/share" requests.
func (s *Server) ShareHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", *allowOriginFlag)
w.Header().Set("Access-Control-Allow-Headers", "Content-Type") // Needed for Safari.
if req.Method != "POST" {
w.Header().Set("Allow", "POST")
http.Error(w, "Method should be POST.", http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(http.MaxBytesReader(w, req.Body, maxSnippetSizeBytes))
if err != nil {
log.Println(err)
http.Error(w, "Server error.", http.StatusInternalServerError)
return
}
id, err := s.Store.StoreSnippet(req.Context(), body)
if err != nil {
log.Println(err)
http.Error(w, "Server error.", http.StatusInternalServerError)
return
}
_, err = io.WriteString(w, id)
if err != nil {
log.Println(err)
return
}
}
// PHandler is the handler for "/p/{{.SnippetId}}" requests.
func (s *Server) PHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", *allowOriginFlag)
if req.Method != "GET" {
w.Header().Set("Allow", "GET")
http.Error(w, "Method should be GET.", http.StatusMethodNotAllowed)
return
}
id := req.URL.Path[len("/p/"):]
err := validateID(id)
if err != nil {
http.Error(w, "Unexpected id format.", http.StatusBadRequest)
return
}
// Set a 3 minute timeout to load and serve the snippet.
ctx, cancel := context.WithTimeout(req.Context(), 3*time.Minute)
defer cancel()
snippet, err := s.Store.LoadSnippet(ctx, id)
if os.IsNotExist(err) {
// Snippet not found.
http.Error(w, "Snippet not found.", http.StatusNotFound)
return
} else if err != nil {
log.Println(err)
http.Error(w, "Server error.", http.StatusInternalServerError)
return
}
defer snippet.Close()
_, err = io.Copy(w, snippet)
if err != nil {
log.Println(err)
return
}
}