1
0
Fork 0

Split out templates, add readiness endpoints

upload-progress
Ambrose Chua 2021-05-23 11:54:30 +08:00
parent bf94a2e2bf
commit 7a2bea5969
15 changed files with 6938 additions and 91 deletions

View File

@ -12,7 +12,8 @@ type credential struct {
SecretKey string SecretKey string
// Region is critical when signing requests. // Region is critical when signing requests.
Region string Region string
// Endpoint is the base URL of the bucket, including the bucket name (in either the domain or path). // Endpoint is the base URL of the bucket, including the bucket name (in
// either the domain or path).
// //
// Example: // Example:
// https://bucketname.s3.us-west-2.amazonaws.com // https://bucketname.s3.us-west-2.amazonaws.com

View File

@ -2,7 +2,6 @@ package main
import ( import (
"errors" "errors"
"log"
"net/http" "net/http"
) )
@ -11,7 +10,6 @@ var errBadRequest = errors.New("bad request")
var errInternalServerError = errors.New("internal server error") var errInternalServerError = errors.New("internal server error")
func errorResponseStatus(w http.ResponseWriter, req *http.Request, err error) { func errorResponseStatus(w http.ResponseWriter, req *http.Request, err error) {
errorMessage := err.Error()
errorStatus := http.StatusInternalServerError errorStatus := http.StatusInternalServerError
if errors.Is(err, errNotFound) { if errors.Is(err, errNotFound) {
@ -22,10 +20,13 @@ func errorResponseStatus(w http.ResponseWriter, req *http.Request, err error) {
errorStatus = http.StatusInternalServerError errorStatus = http.StatusInternalServerError
} }
log.Printf("%s %s: %s", req.Method, req.RequestURI, errorMessage)
w.WriteHeader(errorStatus) w.WriteHeader(errorStatus)
} }
// errorResponse prints the error message in the response body.
//
// Do not use this function when the error message might contain sensitive
// information.
func errorResponse(w http.ResponseWriter, req *http.Request, err error) { func errorResponse(w http.ResponseWriter, req *http.Request, err error) {
errorResponseStatus(w, req, err) errorResponseStatus(w, req, err)
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))

View File

@ -5,11 +5,20 @@ import (
"net/http" "net/http"
) )
type loggerResponseWriter struct {
http.ResponseWriter
statusCode int
}
func (w *loggerResponseWriter) WriteHeader(code int) {
w.statusCode = code
w.ResponseWriter.WriteHeader(code)
}
func middlewareLogger(next http.Handler) http.Handler { func middlewareLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here lw := &loggerResponseWriter{w, http.StatusOK}
log.Printf("%s: %s", r.Method, r.RequestURI) next.ServeHTTP(lw, r)
// Call the next handler, which can be another middleware in the chain, or the final handler. log.Printf("%s %s: %d", r.Method, r.RequestURI, lw.statusCode)
next.ServeHTTP(w, r)
}) })
} }

View File

@ -27,8 +27,8 @@ func main() {
router := mux.NewRouter() router := mux.NewRouter()
router.Use(middlewareLogger) router.Use(middlewareLogger)
router.Methods(http.MethodGet).Path("/readyz").HandlerFunc(readyz)
router.Methods(http.MethodGet).PathPrefix("/assets").Handler(http.FileServer(http.FS(assetsWeb))) router.Methods(http.MethodGet).PathPrefix("/assets").Handler(http.FileServer(http.FS(assetsWeb)))
router.Methods(http.MethodGet).Path("/favicon.ico").Handler(http.FileServer(http.FS(assetsWeb)))
router.Methods(http.MethodGet).Path("/").HandlerFunc(handleCreate) router.Methods(http.MethodGet).Path("/").HandlerFunc(handleCreate)
uploadRouter := router.PathPrefix("/{id}").Subrouter() uploadRouter := router.PathPrefix("/{id}").Subrouter()

23
readyz.go Normal file
View File

@ -0,0 +1,23 @@
package main
import (
"bytes"
"fmt"
"net/http"
)
func readyz(w http.ResponseWriter, req *http.Request) {
status := http.StatusOK
var msg bytes.Buffer
storeErr := globalStore.ping()
if storeErr != nil {
status = http.StatusServiceUnavailable
fmt.Fprintf(&msg, "store: %v\n", storeErr)
} else {
fmt.Fprintf(&msg, "store: ok\n")
}
w.WriteHeader(status)
w.Write(msg.Bytes())
}

24
s3.go
View File

@ -60,7 +60,10 @@ type initiateMultipartUploadResult struct {
UploadID string `xml:"UploadId"` UploadID string `xml:"UploadId"`
} }
func initiateMultipartUpload(key string, cred credential) (initiateMultipartUploadResult, error) { func initiateMultipartUpload(
key string,
cred credential,
) (initiateMultipartUploadResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() defer cancel()
@ -119,7 +122,11 @@ type listPartsResult struct {
Parts []part `xml:"Part"` Parts []part `xml:"Part"`
} }
func listParts(key, uploadID string, cred credential, partNumberMarker uint32) (listPartsResult, error) { func listParts(
key, uploadID string,
cred credential,
partNumberMarker uint32,
) (listPartsResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() defer cancel()
@ -151,7 +158,7 @@ func listParts(key, uploadID string, cred credential, partNumberMarker uint32) (
} }
for i := range result.Parts { for i := range result.Parts {
result.Parts[i].ETag = strings.TrimSuffix(strings.TrimPrefix(result.Parts[i].ETag, "\""), "\"") result.Parts[i].ETag = stripETag(result.Parts[i].ETag)
} }
return result, nil return result, nil
@ -186,7 +193,11 @@ type completeMultipartUploadResult struct {
ETag string ETag string
} }
func completeMultipartUpload(key, uploadID string, parts []completePart, cred credential) (completeMultipartUploadResult, error) { func completeMultipartUpload(
key, uploadID string,
parts []completePart,
cred credential,
) (completeMultipartUploadResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() defer cancel()
@ -230,7 +241,10 @@ func completeMultipartUpload(key, uploadID string, parts []completePart, cred cr
/* abortMultipartUpload */ /* abortMultipartUpload */
func abortMultipartUpload(key, uploadID string, cred credential) error { func abortMultipartUpload(
key, uploadID string,
cred credential,
) error {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() defer cancel()

View File

@ -17,6 +17,7 @@ var errInvalidConnectionType = errors.New("invalid connection type")
type store interface { type store interface {
put(key string, data []byte, expire time.Duration) error put(key string, data []byte, expire time.Duration) error
get(key string) ([]byte, error) get(key string) ([]byte, error)
ping() error
} }
type redisStore struct { type redisStore struct {
@ -51,8 +52,23 @@ func newRedisStore(connection string) (*redisStore, error) {
return &redisStore{client}, nil return &redisStore{client}, nil
} }
func (s *redisStore) ping() error {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
var pong string
err := s.client.Do(ctx, radix.Cmd(&pong, "PING"))
if err != nil {
return err
}
if pong != "PONG" {
return errInternalServerError
}
return nil
}
func (s *redisStore) put(key string, data []byte, expire time.Duration) error { func (s *redisStore) put(key string, data []byte, expire time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() defer cancel()
exists := 0 exists := 0
@ -74,7 +90,7 @@ func (s *redisStore) put(key string, data []byte, expire time.Duration) error {
} }
func (s *redisStore) get(key string) ([]byte, error) { func (s *redisStore) get(key string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel() defer cancel()
var data []byte var data []byte

3
web/foot.tmpl Normal file
View File

@ -0,0 +1,3 @@
</body>
</html>

10
web/head.tmpl Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.}}</title>
<link rel="icon" href="assets/favicon.png" type="image/png">
<link rel="stylesheet" href="assets/bundle.css">
</head>
<body>

6855
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,8 @@
"@uppy/status-bar": "^1.9.3", "@uppy/status-bar": "^1.9.3",
"filesize": "^6.3.0", "filesize": "^6.3.0",
"rollup": "^2.48.0", "rollup": "^2.48.0",
"rollup-plugin-css-only": "^3.1.0", "rollup-plugin-copy": "^3.4.0",
"rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-terser": "^7.0.2" "rollup-plugin-terser": "^7.0.2"
} }
} }

View File

@ -1,6 +1,7 @@
import commonjs from '@rollup/plugin-commonjs'; import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve'; import resolve from '@rollup/plugin-node-resolve';
import css from 'rollup-plugin-css-only'; import postcss from 'rollup-plugin-postcss';
import copy from 'rollup-plugin-copy';
import { terser } from 'rollup-plugin-terser'; import { terser } from 'rollup-plugin-terser';
export default { export default {
@ -15,6 +16,9 @@ export default {
plugins: [ plugins: [
commonjs(), commonjs(),
resolve({ browser: true }), resolve({ browser: true }),
css({ output: 'bundle.css' }), postcss({ extract: true, minimize: true }),
copy({ targets: [
{ src: 'static/favicon.png', dest: 'assets' },
] }),
], ],
}; };

BIN
web/static/favicon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

View File

@ -1,16 +1,8 @@
<!DOCTYPE html> {{template "head.tmpl" "Dropbox Not Found"}}
<html> <div class="upload-wrapper">
<head> <div class="upload">
<meta charset="utf-8"> <h1>Dropbox Not Found</h1>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <p>The dropbox you are looking for doesn't exist or has expired.</p>
<title>Dropbox Not Found</title>
<link rel="stylesheet" href="assets/bundle.css">
</head>
<body>
<div class="upload-wrapper">
<div class="upload">
<h1>Dropbox Not Found</h1>
<p>The dropbox you are looking for doesn't exist or has expired.</p>
</div>
</div> </div>
</body> </div>
{{template "foot.tmpl"}}

View File

@ -1,23 +1,15 @@
<!DOCTYPE html> {{template "head.tmpl" "Dropbox Not Found"}}
<html> <div class="upload-wrapper">
<head> <div class="upload">
<meta charset="utf-8"> <div id="drop-area"></div>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <div id="status-area"></div>
<title>Upload</title> <div id="log-header">
<link rel="stylesheet" href="assets/bundle.css"> <h4>Completed</h4>
</head> <button id="log-clear">Clear</button>
<body>
<div class="upload-wrapper">
<div class="upload">
<div id="drop-area"></div>
<div id="status-area"></div>
<div id="log-header">
<h4>Completed</h4>
<button id="log-clear">Clear</button>
</div>
<div id="log-area"></div>
</div> </div>
<div id="log-area"></div>
</div> </div>
</div>
<script src="assets/bundle.js"></script> <script src="assets/bundle.js"></script>
</body> {{template "foot.tmpl"}}