Skip to content

Commit

Permalink
Merge pull request #62 from NfNitLoop/release-v0.5.1
Browse files Browse the repository at this point in the history
Release v0.5.1
  • Loading branch information
NfNitLoop committed Jul 24, 2021
2 parents 8c67a72 + 964f34d commit 56be9aa
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 9 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
Changelog
=========

Version 0.5.1
=============

Released: July 24, 2021
<https://github.com/NfNitLoop/feoblog/releases/tag/v0.5.1>


Improvements
------------

* Better handling of attachment uploads.
For cases where it already had a copy of an attachment on the server,
FeoBlog would close the HTTP connection early with a 202 status. This
didn't work well in Chrome or with Deno. Now we'll try to be a bit
more friendly to the client.

As an user, you *may* notice slightly faster uploads of duplicate files.
Though I don't expect that that's a common case.

Version 0.5.0
=============

Expand Down
25 changes: 23 additions & 2 deletions src/server/attachments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,16 @@ pub(crate) async fn put_file(
let backend = data.backend_factory.open()?;

let metadata = backend.get_attachment_meta(&user_id, &signature, &file_name)?;
// Drop our pooled connection while we wait for bytes, which could be slow:
drop(backend);

let metadata = match metadata {
Some(d) => d,
None => {
// note: NO drain() here.
// see: https://stackoverflow.com/questions/14250991/is-it-acceptable-for-a-server-to-send-a-http-response-before-the-entire-request
// regarding HTTP errros. This is an error condition anyway.

// If we don't yet have the metadata for a file (provided in its Item), then you can't upload yet.
return Ok(
HttpResponse::Forbidden()
Expand All @@ -78,6 +84,7 @@ pub(crate) async fn put_file(
};

if metadata.exists {
drain(body).await;
return Ok(
HttpResponse::Accepted()
.content_type(PLAINTEXT)
Expand Down Expand Up @@ -119,8 +126,6 @@ pub(crate) async fn put_file(

// Collect the file bytes into a temp file so that we're not using the backend while we wait for the upload:

// Drop our pooled connection while we wait for bytes, which could be slow:
drop(backend);

let file = tempfile().context("Error opening temp file")?;

Expand Down Expand Up @@ -185,6 +190,22 @@ pub(crate) async fn put_file(
);
}


/// If you don't wait to read all the Payload bytes, Actix-Web may close
/// the connection before the client has sent them all. Then the client
/// may behave poorly.
///
/// 1. Chrome has a long pause.
/// 2. Deno fetch() dies on the next request.
/// See: https://github.com/denoland/deno/issues/11513
///
/// SO, here we read the whole payload, even though we don't care what
/// they sent. This seems ripe for DoS but it just seems to be the way
/// browsers and HTTP clients work? 😢
async fn drain(mut payload: Payload) {
while payload.next().await.is_some() {}
}

pub(crate) async fn head_file(
data: Data<AppData>,
Path((user_id, signature, file_name)): Path<(UserID, Signature, String)>,
Expand Down
4 changes: 2 additions & 2 deletions web-client/components/pages/SyncPage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,8 @@ async function syncAttachment({userID, signature, fileName, to, fromServers, tra
try {
await tracker.runSubtask(`Uploading to ${to.url}`, async (tracker) => {
let blob = new Blob([buffer!])
let response = await to.putAttachment(userID, signature, fileName, blob)
tracker.log(`Success. ${response.status}: ${response.statusText}`)
await to.putAttachment(userID, signature, fileName, blob)
tracker.log(`Success.`)
})
} catch (_ignored) {
// The subtask will have recorded the error.
Expand Down
20 changes: 15 additions & 5 deletions web-client/ts/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import * as nacl from "./naclWorker/nacl"
import { bytesToHex, ConsoleLogger, Logger, prefetch } from "./common";
import { tick } from "svelte";

// Before sending files larger than this, we should check whether they exist:
const SMALL_FILE_THRESHOLD = 1024 * 128


// Encapsulates communication with the server(s).
export class Client {

Expand Down Expand Up @@ -96,7 +100,13 @@ export class Client {
return response
}

async putAttachment(userID: UserID, signature: Signature, fileName: string, blob: Blob): Promise<Response> {
async putAttachment(userID: UserID, signature: Signature, fileName: string, blob: Blob): Promise<void> {
// If the file is already in the content store, we can save some bandwidth/time:
if (blob.size > SMALL_FILE_THRESHOLD) {
const {exists} = await this.headAttachment(userID, signature, fileName)
if (exists) return
}

let url = this.attachmentURL(userID, signature, fileName)
let response: Response
try {
Expand All @@ -109,11 +119,11 @@ export class Client {
}

} catch (e) {
console.error("PUT exception:", e)
const {exists} = await this.headAttachment(userID, signature, fileName)
if (exists) return // Someone beat us to the upload, everything's OK.
// else:
throw e
}

return response
}

private attachmentURL(userID: UserID, signature: Signature, fileName: string) {
Expand Down Expand Up @@ -339,7 +349,7 @@ export type AttachmentMeta = {

export type GetItemOptions = {
// When syncing items from one server to another, the receiving server MUST
// perform the verificiation, so verifying in the client is redundant and slow.
// perform the verification, so verifying in the client is redundant and slow.
// Set this flag to skip it.
skipSignatureCheck?: boolean
}
Expand Down

0 comments on commit 56be9aa

Please sign in to comment.