mirror of
https://github.com/simon987/ws_bucket.git
synced 2025-04-10 14:06:46 +00:00
minimum viable (excluding auth)
This commit is contained in:
parent
5a7f3316e6
commit
6048cfbebc
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,3 +10,5 @@
|
|||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
136
api/api.go
Normal file
136
api/api.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/buaazp/fasthttprouter"
|
||||||
|
"github.com/fasthttp/websocket"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var WorkDir, _ = filepath.Abs("./data/")
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = Info{
|
||||||
|
Name: "ws_bucket",
|
||||||
|
Version: "1.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
var motd = WebsocketMotd{
|
||||||
|
Info: info,
|
||||||
|
Motd: "Hello, world",
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebApi struct {
|
||||||
|
server fasthttp.Server
|
||||||
|
db *gorm.DB
|
||||||
|
MotdMessage *websocket.PreparedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func Index(ctx *fasthttp.RequestCtx) {
|
||||||
|
Json(info, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Json(object interface{}, ctx *fasthttp.RequestCtx) {
|
||||||
|
|
||||||
|
resp, err := json.Marshal(object)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Response.Header.Set("Content-Type", "application/json")
|
||||||
|
_, err = ctx.Write(resp)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogRequestMiddleware(h fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
|
return fasthttp.RequestHandler(func(ctx *fasthttp.RequestCtx) {
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"path": string(ctx.Path()),
|
||||||
|
"header": ctx.Request.Header.String(),
|
||||||
|
}).Trace(string(ctx.Method()))
|
||||||
|
|
||||||
|
h(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db *gorm.DB) *WebApi {
|
||||||
|
|
||||||
|
api := &WebApi{}
|
||||||
|
|
||||||
|
logrus.SetLevel(getLogLevel())
|
||||||
|
|
||||||
|
router := fasthttprouter.New()
|
||||||
|
router.GET("/", LogRequestMiddleware(Index))
|
||||||
|
|
||||||
|
router.POST("/client", LogRequestMiddleware(api.CreateClient))
|
||||||
|
|
||||||
|
router.POST("/slot", LogRequestMiddleware(api.AllocateUploadSlot))
|
||||||
|
router.GET("/slot", LogRequestMiddleware(api.ReadUploadSlot))
|
||||||
|
router.GET("/upload", LogRequestMiddleware(api.Upload))
|
||||||
|
|
||||||
|
api.server = fasthttp.Server{
|
||||||
|
Handler: router.Handler,
|
||||||
|
Name: "ws_bucket",
|
||||||
|
}
|
||||||
|
|
||||||
|
api.db = db
|
||||||
|
db.AutoMigrate(&Client{})
|
||||||
|
db.AutoMigrate(&UploadSlot{})
|
||||||
|
|
||||||
|
api.setupMotd()
|
||||||
|
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *WebApi) setupMotd() {
|
||||||
|
var data []byte
|
||||||
|
data, _ = json.Marshal(motd)
|
||||||
|
motdMsg, _ := websocket.NewPreparedMessage(websocket.TextMessage, data)
|
||||||
|
api.MotdMessage = motdMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *WebApi) Run() {
|
||||||
|
address := GetServerAddress()
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"addr": address,
|
||||||
|
}).Info("Starting web server")
|
||||||
|
|
||||||
|
err := api.server.ListenAndServe(address)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Error in ListenAndServe: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetServerAddress() string {
|
||||||
|
serverAddress := os.Getenv("WS_BUCKET_ADDR")
|
||||||
|
if serverAddress == "" {
|
||||||
|
serverAddress = "0.0.0.0:3020"
|
||||||
|
}
|
||||||
|
return serverAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLogLevel() logrus.Level {
|
||||||
|
levelStr := os.Getenv("WS_BUCKET_LOGLEVEL")
|
||||||
|
if levelStr == "" {
|
||||||
|
return logrus.TraceLevel
|
||||||
|
} else {
|
||||||
|
level, err := logrus.ParseLevel(levelStr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
}
|
62
api/auth.go
Normal file
62
api/auth.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *WebApi) CreateClient(ctx *fasthttp.RequestCtx) {
|
||||||
|
|
||||||
|
//TODO: auth
|
||||||
|
|
||||||
|
req := &CreateClientRequest{}
|
||||||
|
err := json.Unmarshal(ctx.Request.Body(), req)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Response.SetStatusCode(400)
|
||||||
|
Json(CreateClientResponse{
|
||||||
|
Ok: false,
|
||||||
|
}, ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !req.IsValid() {
|
||||||
|
ctx.Response.SetStatusCode(400)
|
||||||
|
Json(CreateClientResponse{
|
||||||
|
Ok: false,
|
||||||
|
}, ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := api.createClient(req)
|
||||||
|
|
||||||
|
Json(CreateClientResponse{
|
||||||
|
Ok: true,
|
||||||
|
Secret: client.Secret,
|
||||||
|
}, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *WebApi) createClient(req *CreateClientRequest) *Client {
|
||||||
|
|
||||||
|
client := &Client{
|
||||||
|
Alias: req.Alias,
|
||||||
|
Secret: genSecret(),
|
||||||
|
}
|
||||||
|
|
||||||
|
api.db.Create(client)
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"client": client,
|
||||||
|
}).Info("Created client")
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func genSecret() string {
|
||||||
|
bytes := make([]byte, 32)
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
bytes[i] = byte(48 + rand.Intn(122-48))
|
||||||
|
}
|
||||||
|
return string(bytes)
|
||||||
|
}
|
71
api/models.go
Normal file
71
api/models.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenericResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateClientRequest struct {
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *CreateClientRequest) IsValid() bool {
|
||||||
|
return len(req.Alias) > 3
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateClientResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Secret string `json:"secret,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
ID int64
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllocateUploadSlotRequest struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
MaxSize int64 `json:"max_size"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *AllocateUploadSlotRequest) IsValid() bool {
|
||||||
|
if len(req.Token) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.FileName) <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(WorkDir, req.FileName)
|
||||||
|
pathAbs, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(pathAbs, WorkDir) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.MaxSize < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadSlot struct {
|
||||||
|
MaxSize int64 `json:"max_size"`
|
||||||
|
Token string `gorm:"primary_key",json:"token"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebsocketMotd struct {
|
||||||
|
Info Info `json:"info"`
|
||||||
|
Motd string `json:"motd"`
|
||||||
|
}
|
180
api/slot.go
Normal file
180
api/slot.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/fasthttp/websocket"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const WsBufferSize = 4096
|
||||||
|
|
||||||
|
var Mutexes sync.Map
|
||||||
|
var upgrader = websocket.FastHTTPUpgrader{
|
||||||
|
ReadBufferSize: WsBufferSize,
|
||||||
|
WriteBufferSize: WsBufferSize,
|
||||||
|
EnableCompression: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *WebApi) AllocateUploadSlot(ctx *fasthttp.RequestCtx) {
|
||||||
|
|
||||||
|
req := &AllocateUploadSlotRequest{}
|
||||||
|
err := json.Unmarshal(ctx.Request.Body(), req)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Response.SetStatusCode(400)
|
||||||
|
Json(GenericResponse{
|
||||||
|
Ok: false,
|
||||||
|
}, ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !req.IsValid() {
|
||||||
|
ctx.Response.SetStatusCode(400)
|
||||||
|
Json(CreateClientResponse{
|
||||||
|
Ok: false,
|
||||||
|
}, ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.allocateUploadSlot(req)
|
||||||
|
|
||||||
|
Json(CreateClientResponse{
|
||||||
|
Ok: true,
|
||||||
|
}, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *WebApi) Upload(ctx *fasthttp.RequestCtx) {
|
||||||
|
|
||||||
|
token := string(ctx.Request.Header.Peek("X-Upload-Token"))
|
||||||
|
slot := UploadSlot{}
|
||||||
|
err := api.db.Where("token=?", token).First(&slot).Error
|
||||||
|
if err != nil {
|
||||||
|
ctx.Response.Header.SetStatusCode(400)
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"token": token,
|
||||||
|
}).Warning("Upload slot not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"slot": slot,
|
||||||
|
}).Info("Upgrading connection")
|
||||||
|
|
||||||
|
err = upgrader.Upgrade(ctx, func(ws *websocket.Conn) {
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
err := ws.WritePreparedMessage(api.MotdMessage)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mt, reader, err := ws.NextReader()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if mt != websocket.BinaryMessage {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mu, _ := Mutexes.LoadOrStore(slot.Token, &sync.RWMutex{})
|
||||||
|
mu.(*sync.RWMutex).Lock()
|
||||||
|
path := filepath.Join(WorkDir, slot.FileName)
|
||||||
|
fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, WsBufferSize)
|
||||||
|
totalRead := int64(0)
|
||||||
|
for totalRead < slot.MaxSize {
|
||||||
|
read, err := reader.Read(buf)
|
||||||
|
|
||||||
|
var toWrite int
|
||||||
|
if totalRead+int64(read) > slot.MaxSize {
|
||||||
|
toWrite = int(slot.MaxSize - totalRead)
|
||||||
|
} else {
|
||||||
|
toWrite = read
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fp.Write(buf[:toWrite])
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
totalRead += int64(read)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"totalRead": totalRead,
|
||||||
|
}).Info("Finished reading")
|
||||||
|
err = fp.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mu.(*sync.RWMutex).Unlock()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *WebApi) ReadUploadSlot(ctx *fasthttp.RequestCtx) {
|
||||||
|
|
||||||
|
tokenStr := string(ctx.Request.Header.Peek("X-Upload-Token"))
|
||||||
|
|
||||||
|
slot := UploadSlot{}
|
||||||
|
err := api.db.Where("token=?", tokenStr).First(&slot).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Response.Header.SetStatusCode(404)
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"token": tokenStr,
|
||||||
|
}).Warning("Upload slot not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"slot": slot,
|
||||||
|
}).Info("Reading")
|
||||||
|
|
||||||
|
path := filepath.Join(WorkDir, slot.FileName)
|
||||||
|
|
||||||
|
mu, _ := Mutexes.LoadOrStore(slot.Token, &sync.RWMutex{})
|
||||||
|
mu.(*sync.RWMutex).RLock()
|
||||||
|
fp, err := os.OpenFile(path, os.O_RDONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, WsBufferSize)
|
||||||
|
response := ctx.Response.BodyWriter()
|
||||||
|
for {
|
||||||
|
read, err := fp.Read(buf)
|
||||||
|
_, _ = response.Write(buf[:read])
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mu.(*sync.RWMutex).RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *WebApi) allocateUploadSlot(req *AllocateUploadSlotRequest) {
|
||||||
|
|
||||||
|
slot := &UploadSlot{
|
||||||
|
MaxSize: req.MaxSize,
|
||||||
|
FileName: req.FileName,
|
||||||
|
Token: req.Token,
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"slot": slot,
|
||||||
|
}).Info("Allocated new upload slot")
|
||||||
|
|
||||||
|
api.db.Create(slot)
|
||||||
|
}
|
17
main.go
Normal file
17
main.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/simon987/ws_bucket/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
db, err := gorm.Open("postgres", "host=localhost user=ws_bucket dbname=ws_bucket password=ws_bucket sslmode=disable")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := api.New(db)
|
||||||
|
a.Run()
|
||||||
|
}
|
24
test/auth_test.go
Normal file
24
test/auth_test.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/simon987/ws_bucket/api"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateClient(t *testing.T) {
|
||||||
|
|
||||||
|
r := createClient(api.CreateClientRequest{
|
||||||
|
Alias: "testcreateclient",
|
||||||
|
})
|
||||||
|
|
||||||
|
if r.Ok != true {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createClient(request api.CreateClientRequest) (ar *api.CreateClientResponse) {
|
||||||
|
|
||||||
|
resp := Post("/client", request)
|
||||||
|
UnmarshalResponse(resp, &ar)
|
||||||
|
return
|
||||||
|
}
|
63
test/common.go
Normal file
63
test/common.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/simon987/ws_bucket/api"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Post(path string, x interface{}) *http.Response {
|
||||||
|
|
||||||
|
s := http.Client{}
|
||||||
|
|
||||||
|
body, err := json.Marshal(x)
|
||||||
|
buf := bytes.NewBuffer(body)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "http://"+api.GetServerAddress()+path, buf)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
//ts := time.Now().Format(time.RFC1123)
|
||||||
|
//
|
||||||
|
//mac := hmac.New(crypto.SHA256.New, worker.Secret)
|
||||||
|
//mac.Write(body)
|
||||||
|
//mac.Write([]byte(ts))
|
||||||
|
//sig := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
//
|
||||||
|
//req.Header.Add("X-Worker-Id", strconv.FormatInt(worker.Id, 10))
|
||||||
|
//req.Header.Add("X-Signature", sig)
|
||||||
|
//req.Header.Add("Timestamp", ts)
|
||||||
|
|
||||||
|
r, err := s.Do(req)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(path string, token string) *http.Response {
|
||||||
|
|
||||||
|
s := http.Client{}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "http://"+api.GetServerAddress()+path, nil)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
req.Header.Set("X-Upload-Token", token)
|
||||||
|
|
||||||
|
r, err := s.Do(req)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalResponse(r *http.Response, result interface{}) {
|
||||||
|
data, err := ioutil.ReadAll(r.Body)
|
||||||
|
fmt.Println(string(data))
|
||||||
|
err = json.Unmarshal(data, result)
|
||||||
|
handleErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleErr(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
25
test/main_test.go
Normal file
25
test/main_test.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||||
|
"github.com/simon987/ws_bucket/api"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
|
||||||
|
//db, err := gorm.Open("postgres", "host=localhost user=ws_bucket dbname=ws_bucket password=ws_bucket sslmode=disable")
|
||||||
|
db, err := gorm.Open("sqlite3", ":memory:")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := api.New(db)
|
||||||
|
go a.Run()
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
|
||||||
|
m.Run()
|
||||||
|
}
|
53
test/slot_test.go
Normal file
53
test/slot_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/simon987/ws_bucket/api"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAllocateUploadInvalidMaxSize(t *testing.T) {
|
||||||
|
|
||||||
|
if allocateUploadSlot(api.AllocateUploadSlotRequest{
|
||||||
|
FileName: "valid",
|
||||||
|
Token: "valid",
|
||||||
|
MaxSize: -1,
|
||||||
|
}).Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocateUploadSlotInvalidToken(t *testing.T) {
|
||||||
|
|
||||||
|
if allocateUploadSlot(api.AllocateUploadSlotRequest{
|
||||||
|
FileName: "valid",
|
||||||
|
Token: "",
|
||||||
|
MaxSize: 100,
|
||||||
|
}).Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllocateUploadSlotUnsafePath(t *testing.T) {
|
||||||
|
|
||||||
|
if allocateUploadSlot(api.AllocateUploadSlotRequest{
|
||||||
|
FileName: "../test.png",
|
||||||
|
Token: "valid",
|
||||||
|
MaxSize: 100,
|
||||||
|
}).Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if allocateUploadSlot(api.AllocateUploadSlotRequest{
|
||||||
|
FileName: "test/../../test.png",
|
||||||
|
Token: "valid",
|
||||||
|
MaxSize: 100,
|
||||||
|
}).Ok != false {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func allocateUploadSlot(request api.AllocateUploadSlotRequest) (ar *api.GenericResponse) {
|
||||||
|
resp := Post("/slot", request)
|
||||||
|
UnmarshalResponse(resp, &ar)
|
||||||
|
return
|
||||||
|
}
|
179
test/upload_test.go
Normal file
179
test/upload_test.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fasthttp/websocket"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/simon987/ws_bucket/api"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWebsocketReturnsMotd(t *testing.T) {
|
||||||
|
|
||||||
|
id := uuid.New()
|
||||||
|
allocateUploadSlot(api.AllocateUploadSlotRequest{
|
||||||
|
FileName: "testmotd",
|
||||||
|
MaxSize: 0,
|
||||||
|
Token: id.String(),
|
||||||
|
})
|
||||||
|
|
||||||
|
c := ws(id.String())
|
||||||
|
motd := &api.WebsocketMotd{}
|
||||||
|
err := c.ReadJSON(&motd)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
if len(motd.Motd) <= 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
if len(motd.Info.Version) <= 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebSocketUploadSmallFile(t *testing.T) {
|
||||||
|
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
|
allocateUploadSlot(api.AllocateUploadSlotRequest{
|
||||||
|
FileName: "testfile",
|
||||||
|
Token: id.String(),
|
||||||
|
MaxSize: math.MaxInt64,
|
||||||
|
})
|
||||||
|
|
||||||
|
c := ws(id.String())
|
||||||
|
_, _, err := c.ReadMessage()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
err = c.WriteMessage(websocket.BinaryMessage, []byte("testuploadsmallfile"))
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
err = c.Close()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
resp := readUploadSlot(id.String())
|
||||||
|
|
||||||
|
if bytes.Compare(resp, []byte("testuploadsmallfile")) != 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebSocketUploadOverwritesFile(t *testing.T) {
|
||||||
|
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
|
allocateUploadSlot(api.AllocateUploadSlotRequest{
|
||||||
|
FileName: "testuploadoverwrites",
|
||||||
|
Token: id.String(),
|
||||||
|
MaxSize: math.MaxInt64,
|
||||||
|
})
|
||||||
|
|
||||||
|
c := ws(id.String())
|
||||||
|
_, _, err := c.ReadMessage()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
err = c.WriteMessage(websocket.BinaryMessage, []byte("testuploadsmallfile"))
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
err = c.Close()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
c1 := ws(id.String())
|
||||||
|
_, _, err = c1.ReadMessage()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
err = c1.WriteMessage(websocket.BinaryMessage, []byte("newvalue"))
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
err = c1.Close()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
resp := readUploadSlot(id.String())
|
||||||
|
|
||||||
|
if bytes.Compare(resp, []byte("newvalue")) != 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebSocketUploadLargeFile(t *testing.T) {
|
||||||
|
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
|
allocateUploadSlot(api.AllocateUploadSlotRequest{
|
||||||
|
FileName: "testlargefile",
|
||||||
|
Token: id.String(),
|
||||||
|
MaxSize: math.MaxInt64,
|
||||||
|
})
|
||||||
|
|
||||||
|
c := ws(id.String())
|
||||||
|
_, _, err := c.ReadMessage()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
chunk := make([]byte, 100000)
|
||||||
|
_ = copy(chunk, "test")
|
||||||
|
_ = c.WriteMessage(websocket.BinaryMessage, chunk)
|
||||||
|
|
||||||
|
err = c.Close()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
resp := readUploadSlot(id.String())
|
||||||
|
|
||||||
|
if bytes.Compare(resp, chunk) != 0 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebSocketUploadMaxSize(t *testing.T) {
|
||||||
|
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
|
allocateUploadSlot(api.AllocateUploadSlotRequest{
|
||||||
|
FileName: "testmaxsize",
|
||||||
|
Token: id.String(),
|
||||||
|
MaxSize: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
c := ws(id.String())
|
||||||
|
_, _, err := c.ReadMessage()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
chunk := make([]byte, 100000)
|
||||||
|
_ = copy(chunk, "test")
|
||||||
|
_ = c.WriteMessage(websocket.BinaryMessage, chunk)
|
||||||
|
|
||||||
|
err = c.Close()
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
resp := readUploadSlot(id.String())
|
||||||
|
|
||||||
|
if len(resp) != 10 {
|
||||||
|
t.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readUploadSlot(token string) []byte {
|
||||||
|
|
||||||
|
r := Get("/slot", token)
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(r.Body)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func ws(slot string) *websocket.Conn {
|
||||||
|
|
||||||
|
u := url.URL{Scheme: "ws", Host: "localhost:3021", Path: "/upload"}
|
||||||
|
fmt.Printf("Connecting to %s", u.String())
|
||||||
|
|
||||||
|
header := http.Header{}
|
||||||
|
header.Add("X-Upload-Token", slot)
|
||||||
|
c, _, err := websocket.DefaultDialer.Dial(u.String(), header)
|
||||||
|
handleErr(err)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user