-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
How to stream a large JSON response? #1424
Comments
Unfortunately there isn't an easy one-to-one translation of this code, because at the end of the day Rocket wants the data body to implement
Here is some (untested!) partial code as an example of the state machine approach. struct UsersStream {
state: State,
users: Vec<User>,
pos: 0,
pending: Cursor<Vec<u8>>,
}
enum State { Header, Users, Trailer, Done }
impl Read for UsersStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
loop {
// first, try to read any unfinished data from the buffer
match self.pending.read(buf) {
// end of buffer; need to get more data
Ok(0) => (),
Ok(n) => return Ok(n),
Err(e) => return Err(e),
};
// determine the next data to read
match self.state {
State::Header => {
self.pending = Cursor::new(vec![b'[']);
self.state = State::Users;
}
State::Users => {
// encode the next user
match self.users.get(self.pos) {
Some(user) => {
let mut bytes = vec![b','];
encode_user_to_bytes(user, &mut bytes);
self.pos += 1;
self.pending = Cursor::new(bytes);
}
None => self.state = State::Trailer,
}
}
State::Trailer => {
self.pending = Cursor::new(vec![b']']);
self.state = State::Done;
}
State::Done => return Ok(0),
}
}
}
} |
Hi @jebrosen, Thanks for your response. use rocket::http::ContentType;
use rocket::request::Request;
use rocket::response::{self, Responder, Response};
use serde::Serialize;
use std::io::{Cursor, Read};
#[derive(Serialize, Debug)]
pub struct User {
pub id: u32,
pub lastname: String,
pub firstname: String,
}
#[derive(Debug)]
pub struct UsersStream {
pub state: State,
pub users: Vec<User>,
pub pos: usize,
pub pending: Cursor<Vec<u8>>,
}
#[derive(Debug)]
pub enum State {
Header,
Users,
Trailer,
Done,
}
impl Read for UsersStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
loop {
// first, try to read any unfinished data from the buffer
match self.pending.read(buf) {
// end of buffer; need to get more data
Ok(0) => (),
Ok(n) => return Ok(n),
Err(e) => return Err(e),
};
// determine the next data to read
match self.state {
State::Header => {
self.pending = Cursor::new(vec![b'[']);
self.state = State::Users;
}
State::Users => {
// encode the next user
match self.users.get(self.pos) {
Some(user) => {
let mut bytes = vec![b','];
bytes.append(&mut serde_json::to_vec(user).unwrap());
self.pos += 1;
self.pending = Cursor::new(bytes);
}
None => self.state = State::Trailer,
}
}
State::Trailer => {
self.pending = Cursor::new(vec![b']']);
self.state = State::Done;
}
State::Done => return Ok(0),
}
}
}
}
impl<'r> Responder<'r> for UsersStream {
fn respond_to(self, _: &Request) -> response::Result<'r> {
Response::build()
.sized_body(self.pending)
.header(ContentType::new("application", "json"))
.ok()
}
}
#[get("/big-json-stream")]
pub fn big_json_stream() -> Result<UsersStream, ()> {
let mut v: Vec<User> = Vec::new();
for i in 0..100_000 {
v.push(User {
id: i,
lastname: "My lastname".to_owned(),
firstname: String::from("My firstname"),
});
}
Ok(UsersStream {
state: State::Header,
users: v,
pos: 0,
pending: Cursor::new(vec![]),
})
} Sorry, i'm still newby with rust 😞 |
This looks almost right, except that you want |
Hi @jebrosen, I tried this: impl<S: Seek + Write> Seek for BufStream<S> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.inner.seek(pos)
}
} But i had this error:
So i implemented the sdt::io::Seek trait like this: impl Seek for UsersStream {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.pending.seek(pos)
}
} It compiles but Here all the code: use serde::Serialize;
use std::io::{Cursor, Read, Seek, SeekFrom};
use serde_json;
use rocket::request::Request;
use rocket::response::{self, Response, Responder};
use rocket::http::ContentType;
#[derive(Serialize, Debug)]
pub struct User {
pub id: u32,
pub lastname: String,
pub firstname: String,
}
#[derive(Debug)]
pub struct UsersStream {
pub state: State,
pub users: Vec<User>,
pub pos: usize,
pub pending: Cursor<Vec<u8>>,
}
#[derive(Debug)]
pub enum State { Header, Users, Trailer, Done }
impl Read for UsersStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
loop {
// first, try to read any unfinished data from the buffer
match self.pending.read(buf) {
// end of buffer; need to get more data
Ok(0) => (),
Ok(n) => return Ok(n),
Err(e) => return Err(e),
};
// determine the next data to read
match self.state {
State::Header => {
self.pending = Cursor::new(vec![b'[']);
self.state = State::Users;
}
State::Users => {
// encode the next user
match self.users.get(self.pos) {
Some(user) => {
let mut bytes = vec![b','];
bytes.append(&mut serde_json::to_vec(user).unwrap());
self.pos += 1;
self.pending = Cursor::new(bytes);
}
None => self.state = State::Trailer,
}
}
State::Trailer => {
self.pending = Cursor::new(vec![b']']);
self.state = State::Done;
}
State::Done => return Ok(0),
}
}
}
}
impl<'r> Responder<'r> for UsersStream {
fn respond_to(self, _: &Request) -> response::Result<'r> {
Response::build()
.sized_body(self)
.header(ContentType::new("application", "json"))
.ok()
}
}
impl Seek for UsersStream {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.pending.seek(pos)
}
}
#[get("/big-json-stream")]
pub fn big_json_stream() -> Result<UsersStream, ()> {
let mut v: Vec<User> = Vec::new();
for i in 0..100_000 {
v.push(User {
id: i,
lastname: "My lastname".to_owned(),
firstname: String::from("My firstname"),
});
}
Ok(UsersStream{
state: State::Header,
users: v,
pos: 0,
pending: Cursor::new(vec![]),
})
} |
Whoops. You can't use |
@jebrosen, thank you so much for your help, it works perfectly 🎉 Here the final code: use serde::Serialize;
use std::io::{Cursor, Read, Seek, SeekFrom};
use serde_json;
use rocket::request::Request;
use rocket::response::{self, Response, Responder};
use rocket::http::ContentType;
#[derive(Serialize, Debug)]
pub struct User {
pub id: u32,
pub lastname: String,
pub firstname: String,
}
#[derive(Debug)]
pub struct UsersStream {
pub state: State,
pub users: Vec<User>,
pub pos: usize,
pub pending: Cursor<Vec<u8>>,
}
#[derive(Debug)]
pub enum State { Header, Users, Trailer, Done }
impl Read for UsersStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
loop {
// first, try to read any unfinished data from the buffer
match self.pending.read(buf) {
// end of buffer; need to get more data
Ok(0) => (),
Ok(n) => return Ok(n),
Err(e) => return Err(e),
};
// determine the next data to read
match self.state {
State::Header => {
self.pending = Cursor::new(vec![b'[']);
self.state = State::Users;
}
State::Users => {
// encode the next user
match self.users.get(self.pos) {
Some(user) => {
let mut bytes = vec![b','];
bytes.append(&mut serde_json::to_vec(user).unwrap());
self.pos += 1;
self.pending = Cursor::new(bytes);
}
None => self.state = State::Trailer,
}
}
State::Trailer => {
self.pending = Cursor::new(vec![b']']);
self.state = State::Done;
}
State::Done => return Ok(0),
}
}
}
}
impl<'r> Responder<'r> for UsersStream {
fn respond_to(self, _: &Request) -> response::Result<'r> {
Response::build()
.streamed_body(self)
.header(ContentType::new("application", "json"))
.ok()
}
}
#[get("/big-json-stream")]
pub fn big_json_stream() -> Result<UsersStream, ()> {
let mut v: Vec<User> = Vec::new();
for i in 0..100_000 {
v.push(User {
id: i,
lastname: "My lastname".to_owned(),
firstname: String::from("My firstname"),
});
}
Ok(UsersStream{
state: State::Header,
users: v,
pos: 0,
pending: Cursor::new(vec![]),
})
} Can i create a PR to add this case in the |
The code would need some changes to work with |
OK :) |
@fabienbellanger It's already in master. Let's keep this use-case in mind when resolving #33. |
Hello,
Rocket version : 0.4.5
I'm new with Rust and Rocket, so sorry if my question sounds stupid.
I'm used to stream large JSON with Golang and Echo like this:
I read the streaming section in Rocket documentation, but i definitely do not see how to do with a JSON.
How can i do this with Rocket?
Thanks
The text was updated successfully, but these errors were encountered: