Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Uploads using websocket instead of post. #52

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const (
// EventRedirect sent in order to trigger a browser
// redirect.
EventRedirect = "redirect"
// EventUpload sent for upload handling.
EventUpload = "live:upload"
)

// Event messages that are sent and received by the
Expand Down
14 changes: 14 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ func (h *HttpEngine) _serveWS(ctx context.Context, r *http.Request, session Sess

// Handle events coming from the websocket connection.
go func() {
//var currentUpload *Upload
for {
t, d, err := c.Read(ctx)
if err != nil {
Expand All @@ -330,6 +331,19 @@ func (h *HttpEngine) _serveWS(ctx context.Context, r *http.Request, session Sess
eventErrors <- ErrorEvent{Source: m, Err: err.Error()}
}
}
case EventUpload:
var up Upload
if err := json.Unmarshal(m.Data, &up); err != nil {
internalErrors <- err
break
}
conf := findUploadConfig(sock, up.FieldName)
if conf == nil {
eventErrors <- ErrorEvent{Source: m, Err: ErrUploadNotFound.Error()}
break
}
sock.AssignUpload(conf.Name, &up)
//currentUpload = &up
default:
if err := h.CallEvent(ctx, m.T, sock, m); err != nil {
switch {
Expand Down
4 changes: 2 additions & 2 deletions internal/embed/blob.go

Large diffs are not rendered by default.

27 changes: 25 additions & 2 deletions upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"time"
)

const upKey = "uploads"
Expand Down Expand Up @@ -48,17 +50,29 @@ type UploadConfig struct {

// Upload describes an upload from the client.
type Upload struct {
FieldName string `json:"fieldName"`
Name string
Size int64
Type string
LastModified string
Errors []error
Progress float32
Errors []error `json:"-"`
Progress float32 `json:"-"`

internalLocation string `json:"-"`
bytesRead int64 `json:"-"`
}

// CreateFile creates a temp file location.
func (u *Upload) CreateFile(rootDir string, socketID SocketID) (*os.File, error) {
uploadDir := filepath.Join(rootDir, string(socketID))
f, err := os.Create(filepath.Join(uploadDir, fmt.Sprintf("%d%s", time.Now().UnixNano(), filepath.Ext(u.Name))))
if err != nil {
u.Errors = append(u.Errors, fmt.Errorf("%s upload file creation failed: %w", u.Name, err))
return nil, err
}
return f, nil
}

// File gets an open file reader.
func (u Upload) File() (*os.File, error) {
return os.Open(u.internalLocation)
Expand Down Expand Up @@ -194,3 +208,12 @@ func WithUploadStagingLocation(stagingLocation string) EngineConfig {
return nil
}
}

func findUploadConfig(sock Socket, name string) *UploadConfig {
for _, c := range sock.UploadConfigs() {
if c.Name == name {
return c
}
}
return nil
}
1 change: 1 addition & 0 deletions web/src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const EventBeforeDestroy = "live:beforedestroy";
export const EventDestroyed = "live:destroyed";
export const EventDisconnected = "live:disconnected";
export const EventReconnected = "live:reconnected";
export const EventUpload = "live:upload";

export const ClassConnected = "live-connected";
export const ClassDisconnected = "live-disconnected";
Expand Down
21 changes: 10 additions & 11 deletions web/src/events.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Socket } from "./socket";
import { Forms } from "./forms";
import { Upload } from "./upload";
import { UpdateURLParams, GetParams, GetURLParams, Params } from "./params";
import { EventDispatch, LiveEvent } from "./event";

Expand Down Expand Up @@ -328,13 +329,14 @@ class Submit extends LiveHandler {

const hasFiles = Forms.hasFiles(element as HTMLFormElement);
if (hasFiles === true) {
const request = new XMLHttpRequest();
request.open("POST", "");
request.addEventListener('load', () => {
this.sendEvent(element, params);
});

request.send(new FormData(element as HTMLFormElement));
const formData = new FormData(element as HTMLFormElement);
for (let pair of formData.entries()) {
if (!(pair[1] instanceof File)) {
return;
}
const u = new Upload(pair[0], pair[1]);
u.begin();
};
} else {
this.sendEvent(element, params);
}
Expand All @@ -357,10 +359,7 @@ class Submit extends LiveHandler {
vals[k] = data[k];
});
element.classList.add(`${this.attribute}-loading`);
Socket.sendAndTrack(
new LiveEvent(t, vals, LiveEvent.GetID()),
element
);
Socket.sendAndTrack(new LiveEvent(t, vals, LiveEvent.GetID()), element);
}
}

Expand Down
4 changes: 4 additions & 0 deletions web/src/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ export class Socket {
this.conn.send(e.serialize());
}

static sendFile(blob: Blob) {
this.conn.send(blob);
}

/**
* Called when a ack event comes in. Complete the loop
* with any outstanding tracked events.
Expand Down
49 changes: 49 additions & 0 deletions web/src/upload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Socket } from "./socket";
import { LiveEvent, EventUpload } from "./event";

const bytesPerChunk = 1024 * 20;

interface uploadPrimer {
name: string;
type: string;
size: number;
}

export class Upload {
fieldName: string;
file: File;
name: string;
type: string;
size: number;

constructor(fieldName: string, file: File) {
this.fieldName = fieldName;
this.file = file;
this.name = file.name;
this.type = file.type;
this.size = file.size;
}

async begin() {
this.sendPrimer({
name: this.name,
type: this.type,
size: this.size,
});

const blob = this.file as Blob;
let start = 0;
let end = bytesPerChunk;

while (start < blob.size) {
const slice = blob.slice(start, end);
Socket.sendFile(slice);
start = end;
end = start + bytesPerChunk;
}
}

private sendPrimer(primer: uploadPrimer) {
Socket.send(new LiveEvent(EventUpload, primer));
}
}