Split out templates, add readiness endpoints
parent
bf94a2e2bf
commit
7a2bea5969
|
@ -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
|
||||||
|
|
|
@ -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()))
|
||||||
|
|
17
logger.go
17
logger.go
|
@ -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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
2
main.go
2
main.go
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
24
s3.go
|
@ -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()
|
||||||
|
|
||||||
|
|
20
store.go
20
store.go
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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>
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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' },
|
||||||
|
] }),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 234 B |
|
@ -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"}}
|
||||||
|
|
|
@ -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"}}
|
||||||
|
|
Loading…
Reference in New Issue