-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Support Multipart Forms #106
Comments
Do you know a quick workaround to support it? |
I tried to use mime_multipart::read_multipart like this: |
You should be able to adapt the multipart crate pretty easily here. I can't vouch for it, however, as I haven't tried it myself. |
Thanks for that fast reply! |
Sure! Grabbing lunch now, but can whip something up afterward! |
How was the lunch, more importantly how is multipart integration coming? |
@Zeludon Lunch was great, thanks! 😋 The simplest route towards multipart support is via an existing crate. The most complete of those available is multipart. Unfortunately its API is rather restrictive at the moment, and so it wouldn't be a good fit for Rocket. Nonetheless, I've proposed a change to the API to the author/maintainer at abonander/multipart#58 that would allow its integration with Rocket. |
I'm working on a small utility website where I need to upload a csv file from a form. Has there been any progress for this? Thanks for this great library, I'm in love with it! |
@SergioBenitez If there's any more blockers, let me know. |
I answered a related question on Stack Overflow. Note that there are limitations of this approach. #![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
extern crate multipart;
#[post("/", data = "<upload>")]
fn index(upload: DummyMultipart) -> String {
format!("I read this: {:?}", upload)
}
#[derive(Debug)]
struct DummyMultipart {
alpha: String,
one: i32,
file: Vec<u8>,
}
use std::io::{Cursor, Read};
use rocket::{Request, Data, Outcome};
use rocket::data::{self, FromData};
use multipart::server::Multipart;
impl FromData for DummyMultipart {
type Error = ();
fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
// All of these errors should be reported
let ct = request.headers().get_one("Content-Type").expect("no content-type");
let idx = ct.find("boundary=").expect("no boundary");
let boundary = &ct[(idx + "boundary=".len())..];
let mut d = Vec::new();
data.stream_to(&mut d).expect("Unable to read");
let mut mp = Multipart::with_body(Cursor::new(d), boundary);
// Custom implementation parts
let mut alpha = None;
let mut one = None;
let mut file = None;
mp.foreach_entry(|mut entry| {
match entry.name.as_str() {
"alpha" => {
let t = entry.data.as_text().expect("not text");
alpha = Some(t.into());
},
"one" => {
let t = entry.data.as_text().expect("not text");
let n = t.parse().expect("not number");
one = Some(n);
},
"file" => {
let mut d = Vec::new();
let f = entry.data.as_file().expect("not file");
f.read_to_end(&mut d).expect("cant read");
file = Some(d);
},
other => panic!("No known key {}", other),
}
}).expect("Unable to iterate");
let v = DummyMultipart {
alpha: alpha.expect("alpha not set"),
one: one.expect("one not set"),
file: file.expect("file not set"),
};
// End custom
Outcome::Success(v)
}
}
fn main() {
rocket::ignite().mount("/", routes![index]).launch();
}
|
@shepmaster Thanks for the snippet! One particular concern with your code is that you read all of the form data into memory with no limit. This means that, for instance, if a user uploads a 500MB file, the entire file will reside in memory even though it could have been streamed to disk. Or, worse, if a user sends you data infinitely, the request will exhaust the memory resources on the server, likely crashing it. |
What's the status on native integration? Not much else I can do on my end, AFAIK. |
@SergioBenitez absolutely, and thank you for pointing that out. However, many people don't need to worry about that level security for their own application for whatever reason. A solution in the hand is worth two in the bush. 😺 |
@abonander My primary concern is correctness. Given that each time (three so far) I've tried to use your library I've run into showstoppers regarding correctness, I'm not particularly confident that it will function as required. I'd like to see abonander/multipart#59 closed. Parsing and networking bugs can be some of the most insidious, and they can lead to extreme confusion when they pop up. I'd really, really like to avoid this. |
I tried fuzzing and it found several more bugs; last I tried it, I believe it ran for several hours without issue. Should I let it run overnight? For a week? A month? Should I have it run on CI somehow? I haven't seen any good rule of thumb here. I've added a number of edge cases to the integration test. No new issues have cropped up, unless there's some you haven't told me about. |
@abonander Are you testing potential networking issues somehow? Also, do you have an idea of how the performance of the library compares to some ideal? Unfortunately, I don't think we're ready to add multipart support directly to Rocket just yet. As such, I'm moving this to 0.4. If no breaking changes are required, we might see this as a point release in 0.3. |
What kinds of issues that aren't covered by randomized read sizes in the integration test, or fuzzing? Any I/O errors are propagated upwards.
What would you consider an ideal? |
Is it planned to land soon in rocket ? I am currently lacking this feature for a project... 😄 |
To anyone interested in another workaround, I devised this jQuery function to convert file uploads into base64, avoiding the need for multipart forms. The base64 string is added to a hidden element that replaces the old file input. It should work on IE10 and above but I've only tested in chrome and firefox. This will require upping the limits on all forms. This would be a reasonably viable workaround if you could set size limits per form. $.fn.base64Upload = function () {
return this.each(function () {
var self = this;
if (window.File && window.FileReader && window.FileList && window.Blob) {
var inputName = $(self).attr("name");
var encoded = $("<input>")
.attr("type", "hidden")
.attr("name", inputName)
.insertAfter($(self));
$(self).change(function (event) {
var file = event.target.files[0];
if (file) {
var reader = new FileReader();
reader.onload = function(readerEvt) {
var binaryString = readerEvt.target.result;
encoded.val(btoa(binaryString));
};
reader.readAsBinaryString(file);
}
});
$(self).val("");
$(self).removeAttr("name");
} else {
console.error("Cannot use base64 upload, File APIs are not supported in this browser!");
}
return self;
});
}; Call like this: $("input[type=file]").base64Upload(); |
We're closing in on the 1 year anniversary! Is there a roadmap or list of things that need to be done for this to work? Is it just the multipart crate? |
There is also the formdata crate if multipart is still unsatisfactory. I never got a reply to my last comment so I don't know what the blockers are. I've fixed a number of bugs in multipart since the previous discussion and I'm soon to land some significant API improvements (on |
Any news on that front? :-) |
The core crate builds, I've been slowly fixing the examples. I remember that Rocket wanted to use multipart behind the scenes so I don't know if I should spend time building integration on my end. |
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
Also looking forward to this. 🙊 |
I'll put this here because I don't see it mentionned in this issue, but there is this crate rocket-multipart-form-data that seems to allow to parse multipart forms. |
Since multipart does not work anymore after the latest updates with respect to async, and multipart-async does not support rocket (yet?), I raise the question again: Is anyone working on direct multipart support for rocket? Or what is the intention of the project owners with respect to multipart support? Is there any known solution that works with the current development version? |
I haven't tried it yet, but I think https://github.com/rousan/multer-rs/ works with Rocket. At least I managed to successfully process incoming multipart requests in a pure-hyper application. |
Thanks for the hint, this looks promising. I will give it a try. |
No that I know of - and other issues have been higher priority. The biggest are compiling on
Multipart support that is both correct and user-friendly is surprisingly difficult, even for seemingly "simple" cases. Consider this hypothetical API: #[derive(FromMultipartForm)]
struct UploadForm {
description: String,
#[form(limit = 1*1024*1024)] // 1MB
file1: MultipartFile,
file2: MultipartFile,
}
#[post("/", data = "<upload_form>")]
fn upload(upload_form: UploadForm) { } This poses several design questions:
Some other rough ideas, all with different tradeoffs chosen from the above: struct UploadForm {
description: String,
file1: Vec<u8>,
file2: File,
}
// "without #[derive]". This provides marginal value over using a multipart crate directly, since the interface would be similar.
impl FromMultipartForm for UploadForm {
// similar trait to FromForm
}
// "magic attribute". This is different from `#[derive]` because it help enforce that values could come in any order.
#[rocket::multipart_form]
impl FromMultipartForm for UploadForm {
async fn description(&mut self, value: String) { /* ... */ }
async fn file1(&mut self, value: MultipartFile) { /* ... */ }
async fn file2(&mut self, value: MultipartFile) { /* ... */ }
}
There are a few options, both of which rely on the fact that |
@jebrosen thanks for your detailed answer and sharing your ideas. I agree, implementing multipart in a robust, safe and user-friendly way is far from being trivial, therefore I hoped I don't have to implement my own workaround. However, I understand multipart support has currently not the highest priority. I probably will try to use multer-rs which supports AsyncRead, but have to upgrade some other of my project's dependencies to async first before I could seriously work on that. What do you think about an approach that supported types in the MultipartForm struct must match the ContentType of the uploaded form (with some allowed conversions)? Storage in memory vs. storing as temp file could be solved by either an attribute or special sub type. |
Hi, I did not have a look at It is not comprehensibly tested because we still have a long way to go if we want to bump our If that helps... Cheers. |
Hi folks, I see a controversy (soft euphemism) in another issue about the release of 0.5, stable rust, and the remaining tasks to do before 0.5 is out. This multipart forms support is one of them. If eventually one of myselves have some time to tackle that particular task, what would be the requirements for a PR to be accepted? I am particularly thinking about:
|
I have already implemented this and all other 0.5 milestone issues, with the exception of #21 which was implemented by @jebrosen and @hawkw, so any efforts to implement this feature would be duplicated. Due to our standards for code quality, usability, and robustness, committing the changes upstream takes longer than you might expect. Nevertheless, you'll see this issue be closed very soon. |
OK, cool, I'm looking forward to seeing that. |
Nice, how we can use multipart in Rocket now? using version 0.5 or there is a workaround that works with version 0.4? |
@hamidrezakp It is already possible to handle multipart forms in a manual way, by passing the request |
Thank you, yeah i am using |
Since this seems to have a well working helper crate, and is on the list for 0.5 milestones, could this maybe be pushed to 0.6? I would very much like to see a release of 0.5 expedited. |
Hello @jebrosen and @SergioBenitez thankyou for an already amazing experience with rust web development. I had a good time reading your guide and experimenting with rocket, tera and postgresql. I need multipart forms for a prototype. I would use a not yet stable api for the prototype. Is there a way I can use the rocket implementation right now without using a workaround which I probably will abandon soon? I am considering myself to be a beginner rust programmer. I hope you are ok with me asking this newbie question. |
Routing: * Unicode characters are accepted anywhere in route paths. (#998) * Dyanmic query values can (and must) be any `FromForm` type. The `Form` type is no longer useable in any query parameter type. Capped * A new `Capped` type is used to indicate when data has been truncated due to incoming data limits. It allows checking whether data is complete or truncated. `DataStream` methods returns `Capped` types. * Several `Capped<T>` types implement `FromData`, `FromForm`. * HTTP 413 (Payload Too Large) errors are now returned when the data limit is exceeded. (resolves #972) Hierarchical Limits * Data limits are now hierarchical, delimited with `/`. A limit of `a/b/c` falls back to `a/b` then `a` when not set. Temporary Files * A new `TempFile` data and form guard allows streaming data directly to a file which can then be persisted. * A new `temp_dir` config parameter specifies where to store `TempFile`. * The limits `file` and `file/$ext`, where `$ext` is the file extension, determines the data limit for a `TempFile`. Forms Revamp * All form related types now reside in a new `form` module. * Multipart forms are supported. (resolves #106) * Collections are supported in body forms and queries. (resolves #205) * Nested forms and structures are supported. (resolves #313) * Form fields can be ad-hoc validated with `#[field(value = expr)]`. Core: * `&RawStr` no longer implements `FromParam`. * `&str` implements `FromParam`, `FromData`, `FromForm`. * `FromTransformedData` was removed. * `FromData` gained a lifetime for use with request-local data. * All dynamic paramters in a query string must typecheck as `FromForm`. * `FromFormValue` removed in favor of `FromFormField`. * Dyanmic paramters, form values are always percent-decoded. * The default error HTML is more compact. * `&Config` is a request guard. * The `DataStream` interface was entirely revamped. * `State` is only exported via `rocket::State`. * A `request::local_cache!()` macro was added for storing values in request-local cache without consideration for type uniqueness by using a locally generated anonymous type. * `Request::get_param()` is now `Request::param()`. * `Request::get_segments()` is now `Request::segments()`, takes a range. * `Request::get_query_value()` is now `Request::query_value()`, can parse any `FromForm` including sequences. * `std::io::Error` implements `Responder` as `Debug<std::io::Error>`. * `(Status, R)` where `R: Responder` implements `Responder` by setting overriding the `Status` of `R`. * The name of a route is printed first during route matching. * `FlashMessage` now only has one lifetime generic. HTTP: * `RawStr` implements `serde::{Serialize, Deserialize}`. * `RawStr` implements _many_ more methods, in particular, those related to the `Pattern` API. * `RawStr::from_str()` is now `RawStr::new()`. * `RawStr::url_decode()` and `RawStr::url_decode_lossy()` only allocate as necessary, return `Cow`. * `(Status, R)` where `R: Responder` is a responder that overwrites the status of `R` to `Status`. * `Status` implements `Default` with `Status::Ok`. * `Status` implements `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`. * Authority and origin part of `Absolute` can be modified with new `Absolute::{with,set}_authority()`, `Absolute::{with,set}_origin()` methods. * `Origin::segments()` was removed in favor of methods split into query and path parts and into raw and decoded parts. * The `Segments` iterator is signficantly smarter. Returns `&str`. * `Segments::into_path_buf()` is now `Segments::to_path_buf()`, doesn't consume. * A new `QuerySegments` is the analogous query segment iterator. * Once set, the `expires` field on private cookies is not overwritten. (resolves #1506) * `Origin::path()` and `Origin::query()` return `&RawStr`, not `&str`. Codegen: * Preserve more spans in `uri!` macro. * `FromFormValue` derive removed; `FromFormField` added. * The `form` `FromForm` and `FromFormField` field attribute is now named `field`. `#[form(field = ..)]` is now `#[form(name = ..)]`. Examples: * `form_validation` and `form_kitchen_sink` removed in favor of `forms` * `rocket_contrib::Json` implements `FromForm`. * The `json!` macro is exported as `rocket_contrib::json::json`. * `rocket_contrib::MsgPack` implements `FromForm`. * Added clarifying docs to `StaticFiles`. * The `hello_world` example uses unicode in paths. Internal: * Codegen uses new `exports` module with the following conventions: - Locals starts with `__` and are lowercased. - Rocket modules start with `_` are are lowercased. - Stdlib types start with `_` are are titlecased. - Rocket types are titlecased. * A `header` module was added to `http`, contains header types. * `SAFETY` is used as doc-string keyword for `unsafe` related comments. * The `Uri` parser no longer recognizes Rocket route URIs.
Routing: * All UTF-8 characters are accepted anywhere in route paths. (#998) * `path` is now `uri` in `route` attribute: `#[route(GET, path = "..")]` becomes `#[route(GET, uri = "..")]`. Forms Revamp * All form related types now reside in a new `form` module. * Multipart forms are supported. (resolves #106) * Collections are supported in body forms and queries. (resolves #205) * Nested forms and structures are supported. (resolves #313) * Form fields can be ad-hoc validated with `#[field(value = expr)]`. * `FromFormValue` is now `FromFormField`, blanket implements `FromForm`. * Form field values are always percent-decoded apriori. Temporary Files * A new `TempFile` data and form guard allows streaming data directly to a file which can then be persisted. * A new `temp_dir` config parameter specifies where to store `TempFile`. * The limits `file` and `file/$ext`, where `$ext` is the file extension, determines the data limit for a `TempFile`. Capped * A new `Capped` type is used to indicate when data has been truncated due to incoming data limits. It allows checking whether data is complete or truncated. `DataStream` methods return `Capped` types. * Several `Capped<T>` types implement `FromData`, `FromForm`. * HTTP 413 (Payload Too Large) errors are now returned when data limits are exceeded. (resolves #972) Hierarchical Limits * Data limits are now hierarchical, delimited with `/`. A limit of `a/b/c` falls back to `a/b` then `a`. Core * `&RawStr` no longer implements `FromParam`. * `&str` implements `FromParam`, `FromData`, `FromForm`. * `FromTransformedData` was removed. * `FromData` gained a lifetime for use with request-local data. * The default error HTML is more compact. * `&Config` is a request guard. * The `DataStream` interface was entirely revamped. * `State` is only exported via `rocket::State`. * A `request::local_cache!()` macro was added for storing values in request-local cache without consideration for type uniqueness by using a locally generated anonymous type. * `Request::get_param()` is now `Request::param()`. * `Request::get_segments()` is now `Request::segments()`, takes a range. * `Request::get_query_value()` is now `Request::query_value()`, can parse any `FromForm` including sequences. * `std::io::Error` implements `Responder` as `Debug<std::io::Error>`. * `(Status, R)` where `R: Responder` implements `Responder` by overriding the `Status` of `R`. * The name of a route is printed first during route matching. * `FlashMessage` now only has one lifetime generic. HTTP * `RawStr` implements `serde::{Serialize, Deserialize}`. * `RawStr` implements _many_ more methods, in particular, those related to the `Pattern` API. * `RawStr::from_str()` is now `RawStr::new()`. * `RawStr::url_decode()` and `RawStr::url_decode_lossy()` only allocate as necessary, return `Cow`. * `Status` implements `Default` with `Status::Ok`. * `Status` implements `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`. * Authority and origin part of `Absolute` can be modified with new `Absolute::{with,set}_authority()`, `Absolute::{with,set}_origin()` methods. * `Origin::segments()` was removed in favor of methods split into query and path parts and into raw and decoded versions. * The `Segments` iterator is smarter, returns decoded `&str` items. * `Segments::into_path_buf()` is now `Segments::to_path_buf()`. * A new `QuerySegments` is the analogous query segment iterator. * Once set, `expires` on private cookies is not overwritten. (resolves #1506) * `Origin::path()` and `Origin::query()` return `&RawStr`, not `&str`. Codegen * Preserve more spans in `uri!` macro. * Preserve spans `FromForm` field types. * All dynamic parameters in a query string must typecheck as `FromForm`. * `FromFormValue` derive removed; `FromFormField` added. * The `form` `FromForm` and `FromFormField` field attribute is now named `field`. `#[form(field = ..)]` is now `#[field(name = ..)]`. Contrib * `Json` implements `FromForm`. * `MsgPack` implements `FromForm`. * The `json!` macro is exported as `rocket_contrib::json::json!`. * Added clarifying docs to `StaticFiles`. Examples * `form_validation` and `form_kitchen_sink` removed in favor of `forms`. * The `hello_world` example uses unicode in paths. * The `json` example only allocates as necessary. Internal * Codegen uses new `exports` module with the following conventions: - Locals starts with `__` and are lowercased. - Rocket modules start with `_` and are lowercased. - `std` types start with `_` and are titlecased. - Rocket types are titlecased. * A `header` module was added to `http`, contains header types. * `SAFETY` is used as doc-string keyword for `unsafe` related comments. * The `Uri` parser no longer recognizes Rocket route URIs.
Routing: * All UTF-8 characters are accepted anywhere in route paths. (#998) * `path` is now `uri` in `route` attribute: `#[route(GET, path = "..")]` becomes `#[route(GET, uri = "..")]`. Forms Revamp * All form related types now reside in a new `form` module. * Multipart forms are supported. (resolves #106) * Collections are supported in body forms and queries. (resolves #205) * Nested forms and structures are supported. (resolves #313) * Form fields can be ad-hoc validated with `#[field(value = expr)]`. * `FromFormValue` is now `FromFormField`, blanket implements `FromForm`. * Form field values are always percent-decoded apriori. Temporary Files * A new `TempFile` data and form guard allows streaming data directly to a file which can then be persisted. * A new `temp_dir` config parameter specifies where to store `TempFile`. * The limits `file` and `file/$ext`, where `$ext` is the file extension, determines the data limit for a `TempFile`. Capped * A new `Capped` type is used to indicate when data has been truncated due to incoming data limits. It allows checking whether data is complete or truncated. `DataStream` methods return `Capped` types. * Several `Capped<T>` types implement `FromData`, `FromForm`. * HTTP 413 (Payload Too Large) errors are now returned when data limits are exceeded. (resolves #972) Hierarchical Limits * Data limits are now hierarchical, delimited with `/`. A limit of `a/b/c` falls back to `a/b` then `a`. Core * `&RawStr` no longer implements `FromParam`. * `&str` implements `FromParam`, `FromData`, `FromForm`. * `FromTransformedData` was removed. * `FromData` gained a lifetime for use with request-local data. * The default error HTML is more compact. * `&Config` is a request guard. * The `DataStream` interface was entirely revamped. * `State` is only exported via `rocket::State`. * A `request::local_cache!()` macro was added for storing values in request-local cache without consideration for type uniqueness by using a locally generated anonymous type. * `Request::get_param()` is now `Request::param()`. * `Request::get_segments()` is now `Request::segments()`, takes a range. * `Request::get_query_value()` is now `Request::query_value()`, can parse any `FromForm` including sequences. * `std::io::Error` implements `Responder` as `Debug<std::io::Error>`. * `(Status, R)` where `R: Responder` implements `Responder` by overriding the `Status` of `R`. * The name of a route is printed first during route matching. * `FlashMessage` now only has one lifetime generic. HTTP * `RawStr` implements `serde::{Serialize, Deserialize}`. * `RawStr` implements _many_ more methods, in particular, those related to the `Pattern` API. * `RawStr::from_str()` is now `RawStr::new()`. * `RawStr::url_decode()` and `RawStr::url_decode_lossy()` only allocate as necessary, return `Cow`. * `Status` implements `Default` with `Status::Ok`. * `Status` implements `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`. * Authority and origin part of `Absolute` can be modified with new `Absolute::{with,set}_authority()`, `Absolute::{with,set}_origin()` methods. * `Origin::segments()` was removed in favor of methods split into query and path parts and into raw and decoded versions. * The `Segments` iterator is smarter, returns decoded `&str` items. * `Segments::into_path_buf()` is now `Segments::to_path_buf()`. * A new `QuerySegments` is the analogous query segment iterator. * Once set, `expires` on private cookies is not overwritten. (resolves #1506) * `Origin::path()` and `Origin::query()` return `&RawStr`, not `&str`. Codegen * Preserve more spans in `uri!` macro. * Preserve spans `FromForm` field types. * All dynamic parameters in a query string must typecheck as `FromForm`. * `FromFormValue` derive removed; `FromFormField` added. * The `form` `FromForm` and `FromFormField` field attribute is now named `field`. `#[form(field = ..)]` is now `#[field(name = ..)]`. Contrib * `Json` implements `FromForm`. * `MsgPack` implements `FromForm`. * The `json!` macro is exported as `rocket_contrib::json::json!`. * Added clarifying docs to `StaticFiles`. Examples * `form_validation` and `form_kitchen_sink` removed in favor of `forms`. * The `hello_world` example uses unicode in paths. * The `json` example only allocates as necessary. Internal * Codegen uses new `exports` module with the following conventions: - Locals starts with `__` and are lowercased. - Rocket modules start with `_` and are lowercased. - `std` types start with `_` and are titlecased. - Rocket types are titlecased. * A `header` module was added to `http`, contains header types. * `SAFETY` is used as doc-string keyword for `unsafe` related comments. * The `Uri` parser no longer recognizes Rocket route URIs.
I'm trying to use the rocket client to unit-test this, any documentation on how the client can send multipart requests? |
Thanks for the implementation, its easy and clean. However, I have issues that the file here is some code:
here is the curl command I used (I used Insomnia)
Would there any reason that the file name returns |
Your code is not using Rocket's form support at all, but instead, reading the raw data into a fn handle(file: Form<TempFile<'_>>) {
let name = file.name();
} There is also no reason to use |
Rocket currently doesn't provide built-in support for multipart forms, but it should!
The text was updated successfully, but these errors were encountered: