sammhicks / picoserve Goto Github PK
View Code? Open in Web Editor NEWAn async no_std HTTP server suitable for bare-metal environments, heavily inspired by axum
License: MIT License
An async no_std HTTP server suitable for bare-metal environments, heavily inspired by axum
License: MIT License
I'm experiencing some issues when there are multiple concurrent requests.
https://github.com/flyaruu/esp32-http-api
This hello-world repo works fine on a esp32 as long as I keep the request speed reasonable, it keeps working, but if I go a bit wild (e.g. holding the reload button in a browser) it will stop accepting connections and never recover.
Things that seem to help a little bit:
That the performance is limited, that is reasonable, but the breaking and never recovering is a bit of an issue.
Any ideas? Do you see it on other platforms as well? Or is it a esp32 thing?
Hello,
is there any reason why Config::new
cannot be a const
function?
Hi, I'd like to return a response with a response header aside from just a body.
Before I just returned a &str, that worked fine, now I'm attempting this:
async fn get_root()-> impl IntoResponse {
Response::new(OK, "hello world!")
.with_headers(("Connection","close"))
}
But that errors:
type `picoserve::response::BodyHeaders` is private
Is this the correct way to do this?
Hey,
Getting this weird error where I'm connecting to port 80 and have my routes setup for my app properly, but I can only access either the http or the websocket connection not both at the same time on the same port.
Thoughts?
Thanks
cargo build
Blocking waiting for file lock on package cache
Updating `mirror` index
Updating git repository `https://github.com/embassy-rs/embassy.git`
error: failed to select a version for `embassy-time-driver`.
... required by package `embassy-time v0.3.0`
... which satisfies dependency `embassy-time = "^0.3.0"` of package `picoserve v0.10.2`
... which satisfies dependency `picoserve = "^0.10.2"` of package `lot-http v0.1.0 (/Users/shuanghuiyan/Workspace/my-projects/lot-http)`
versions that meet the requirements `^0.1.0` are: 0.1.0
the package `embassy-time-driver` links to the native library `embassy-time`, but it conflicts with a previous package which links to `embassy-time` as well:
package `embassy-time-driver v0.1.0 (https://github.com/embassy-rs/embassy.git#4d4cbc0d)`
... which satisfies git dependency `embassy-time-driver` of package `lot-http v0.1.0 (/Users/shuanghuiyan/Workspace/my-projects/lot-http)`
Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "embassy-time"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links.
failed to select a version for `embassy-time-driver` which could resolve this conflict
Hello,
Awesome crate, thanks for making it.
For the life of me I can't get the proper formatted response in the browser (Chrome, Mozilla, Safari) when constructing the response in no_std
The code works but the response I get is the Raw
output not the bold Hello Rust!
async fn get_site() -> Response<BodyHeaders, ContentBody<&'static str>> {
Response::ok("
<html>\
<body>\
<h1>Hello Rust!</h1>\
</body>\
</html>\r\n\
")
}
//.... other generic code
loop{
//.... other generic code
let app = Router::new().route("/", get(get_site));
//.... other generic code
}
With the following types:
#[derive(Serialize)]
struct EmptyResponse {}
#[derive(Serialize)]
struct SingleEmptyField {
field: EmptyResponse,
}
If you try to return Json(SingleEmptyField { field: EmptyResponse {}})
, you get {"field":}}
as a response while the expected is {"field":{}}
. If you try to return Json(EmptyResponse {})
you get }
instead of {}
.
This is needed to implement an MJPEG stream. A typical multipart response looks something like this.
HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=myboundary
--myboundary
Content-Type: image/jpeg
Content-Length: 12345
[image 1 encoded jpeg data]
--myboundary
Content-Type: image/jpeg
Content-Length: 45678
[image 2 encoded jpeg data]
At the moment I can't implement this outside the library because the Content
trait mandates that I provide a content_length()
and I don't think there's any public way to create a (custom) Response
without a Content-Length
header.
So I guess this has to implemented inside the library similar to SSE and WebSockets?
I'm imagining something like the EventStream
struct, A MixedReplace
struct that takes two parameters, a boundary string and a ContentSource
trait. The ContentSource
trait just allows the user to write new Content
s. (I'm not 100% sure if Content
is the right trait for this, was just my first thought)
Side note: Firefox seems to not mind a Content-Length
atm, so specifying a value like 1000000
allows me to workaround this issue while developing and I'm able to stream small JPEGs with this.
(This library is fantastic btw! Thanks for building it and sharing it!)
Hey its me again haha.
I'm trying to create a function for setting up multiple ports with various routes using a single function to conserve memory on my device, however i'm running into a type
error when setting up the Router
(which is understandable) between IntoResponse
and WebSocketUpgrade
and a little lost on what to do. Any advice?
Also note - I did split it into two separate (http and ws) however i still can't get two ws ports to spawn for some reason (i believe resource issue) just kind of at my wits end.
mismatched types
expected fn item `fn() -> impl Future<Output = impl picoserve::response::IntoResponse> {get_site}`
found closure `{closure@src/network/server.rs:94:44: 94:71}`
#[embassy_executor::task]
pub async fn setup_serve(
stack: &'static Stack<WifiDevice<'static, WifiStaDevice>>,
config: &'static picoserve::Config<Duration>,
port: u16,
)->!{
let mut rx_buffer = [0; 1024];
let mut tx_buffer = [0; 1024];
let app: Router<_, _, _> = match port {
80 => {
// HTTP router configuration
Router::new().route("/", get(get_site))
}
81 | 82 => {
// WebSocket router configuration
Router::new().route("/ws", get(|upgrade: WebSocketUpgrade| {
upgrade.on_upgrade(crate::network::websockets::WebsocketHandler {})
}))
}
_ => unimplemented!(), // Handle other cases if needed
};
println!("Starting Websocket Servers");
loop {
let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
log::info!("Listening on TCP:{}...", port);
println!("Listening on TCP:{}...", port);
if let Err(e) = socket.accept(port).await {
log::warn!("accept error: {:?}", e);
continue;
}
log::info!(
"Received connection from {:?}",
socket.remote_endpoint()
);
let (socket_rx, socket_tx) = socket.split();
match picoserve::serve(
&app,
EmbassyTimer,
config,
&mut [0; 2048],
socket_rx,
socket_tx,
)
.await
{
Ok(handled_requests_count) => {
log::info!(
"{handled_requests_count} requests handled from {:?}",
socket.remote_endpoint()
);
}
Err(err) => log::error!("{err:?}"),
}
}
}
hey could use some advice on why this is happening.
struct EmbassyTimer;
impl picoserve::Timer for EmbassyTimer {
type Duration = embassy_time::Duration;
type TimeoutError = embassy_time::TimeoutError;
async fn run_with_timeout<F: core::future::Future>(
&mut self,
duration: Self::Duration,
future: F,
) -> Result<F::Output, Self::TimeoutError> {
embassy_time::with_timeout(duration, future).await
}
}
async fn get_root()-> impl IntoResponse {
"Hello World"
}
#[embassy_executor::task]
pub async fn web_task(
stack: &'static Stack<WifiDevice<'static, WifiStaDevice>>,
config: &'static picoserve::Config<Duration>,
//sender: Sender<'static, NoopRawMutex, MoveCommand,QUEUE_SIZE>
) -> ! {
let mut rx_buffer = [0; 1024];
let mut tx_buffer = [0; 1024];
loop {
if stack.is_link_up() {
break;
}
Timer::after(Duration::from_millis(500)).await;
}
println!("Waiting to get IP address...");
loop {
if let Some(config) = stack.config_v4() {
println!("Got IP: {}", config.address);
break;
}
Timer::after(Duration::from_millis(500)).await;
}
loop {
let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
log::info!("Listening on TCP:80...");
if let Err(e) = socket.accept(80).await {
log::warn!("accept error: {:?}", e);
continue;
}
log::info!(
"Received connection from {:?}",
socket.remote_endpoint()
);
let (socket_rx, socket_tx) = socket.split();
let app = Router::new()
.route("/", get(get_root))
;
match picoserve::serve(
&app,
EmbassyTimer,
config,
&mut [0; 2048],
socket_rx,
socket_tx,
)
.await
{
Ok(handled_requests_count) => {
log::info!(
"{handled_requests_count} requests handled from {:?}",
socket.remote_endpoint()
);
}
Err(err) => log::error!("{err:?}"),
}
}
}
#[embassy_executor::task]
pub async fn connection(mut controller: WifiController<'static>) {
println!("start connection task");
loop {
match esp_wifi::wifi::get_wifi_state() {
WifiState::StaConnected => {
// wait until we're no longer connected
controller.wait_for_event(WifiEvent::StaDisconnected).await;
Timer::after(Duration::from_millis(5000)).await
}
_ => {}
}
if !matches!(controller.is_ap_enabled(), Ok(true)) {
let client_config = Configuration::Client(ClientConfiguration {
ssid: SSID.into(),
password: PASSWORD.into(),
..Default::default()
});
controller.set_configuration(&client_config).unwrap();
println!("Starting wifi");
controller.start().await.unwrap();
println!("Wifi started!");
}
println!("About to connect...");
match controller.connect().await {
Ok(_) => println!("Wifi connected!"),
Err(e) => {
println!("Failed to connect to wifi: {e:?}");
Timer::after(Duration::from_millis(5000)).await
}
}
}
}
#[embassy_executor::task]
pub async fn net_task(stack: &'static Stack<WifiDevice<'static,WifiStaDevice >>) {
stack.run().await
}
left: `*mut [async fn body@<&str as picoserve::response::IntoResponse>::write_to<picoserve::response::ResponseStream<&mut &mut embassy_net::tcp::TcpReader<'_>, &mut embassy_net::tcp::TcpWriter<'_>>>::{closure#0}]`,
right: `*mut [async fn body@<&str as picoserve::response::IntoResponse>::write_to<picoserve::response::ResponseStream<&mut &mut embassy_net::tcp::TcpReader<'_>, &mut embassy_net::tcp::TcpWriter<'_>>>::{closure#0}]`: unexpected initial operand type', /home/rust/rust/compiler/rustc_codegen_ssa/src/mir/locals.rs:46:21
stack backtrace:
0: 0x7faa9acc782d - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h42c9bd351a3ba23a
1: 0x7faa9ad6adff - core::fmt::write::he924c8a6086ba81f
2: 0x7faa9acf2af7 - std::io::Write::write_fmt::he436599ec2c6627c
3: 0x7faa9acc75e5 - std::sys_common::backtrace::print::h5304f99ffa5b6692
4: 0x7faa9ad0c7d3 - std::panicking::default_hook::{{closure}}::h8bda132361c184fa
5: 0x7faa9ad0c4e2 - std::panicking::default_hook::h6601e926d2a538e4
6: 0x7faa9bae38bb - rustc_driver_impl[c8b89ddd6b0e3357]::install_ice_hook::{closure#0}
7: 0x7faa9ad0d057 - std::panicking::rust_panic_with_hook::h2f963e18465786ef
8: 0x7faa9acc8117 - std::panicking::begin_panic_handler::{{closure}}::hafe668c1547a5d7b
9: 0x7faa9acc7926 - std::sys_common::backtrace::__rust_end_short_backtrace::hb084cc80883bb58b
10: 0x7faa9ad0cbf2 - rust_begin_unwind
11: 0x7faa9aca7c53 - core::panicking::panic_fmt::h7b7ab3fb5fa6c34f
12: 0x7faa9aca8131 - core::panicking::assert_failed_inner::h3a31df9dd62de533
13: 0x7faa9b7ea23b - core[ba37dea25f4d3bf]::panicking::assert_failed::<rustc_middle[390b614d8a4d42db]::ty::Ty, rustc_middle[390b614d8a4d42db]::ty::Ty>
14: 0x7faa9be53e6a - rustc_codegen_ssa[17da4602a24c95d7]::mir::codegen_mir::<rustc_codegen_llvm[aae5e29ef0a87e02]::builder::Builder>
15: 0x7faa9be6c6f2 - rustc_codegen_ssa[17da4602a24c95d7]::base::codegen_instance::<rustc_codegen_llvm[aae5e29ef0a87e02]::builder::Builder>
16: 0x7faa9be4ce05 - rustc_codegen_llvm[aae5e29ef0a87e02]::base::compile_codegen_unit::module_codegen
17: 0x7faa9bd9da56 - <rustc_middle[390b614d8a4d42db]::dep_graph::dep_node::DepKind as rustc_query_system[870567952b1039d0]::dep_graph::DepKind>::with_deps::<<rustc_query_system[870567952b1039d0]::dep_graph::graph::DepGraphData<rustc_middle[390b614d8a4d42db]::dep_graph::dep_node::DepKind>>::with_task<rustc_middle[390b614d8a4d42db]::ty::context::TyCtxt, rustc_span[927b0836cec2b3bf]::symbol::Symbol, rustc_codegen_ssa[17da4602a24c95d7]::ModuleCodegen<rustc_codegen_llvm[aae5e29ef0a87e02]::ModuleLlvm>>::{closure#0}::{closure#0}, rustc_codegen_ssa[17da4602a24c95d7]::ModuleCodegen<rustc_codegen_llvm[aae5e29ef0a87e02]::ModuleLlvm>>
18: 0x7faa9be4c769 - rustc_codegen_llvm[aae5e29ef0a87e02]::base::compile_codegen_unit
19: 0x7faa9be6bd9d - rustc_codegen_ssa[17da4602a24c95d7]::base::codegen_crate::<rustc_codegen_llvm[aae5e29ef0a87e02]::LlvmCodegenBackend>
20: 0x7faa9bd6096f - <rustc_codegen_llvm[aae5e29ef0a87e02]::LlvmCodegenBackend as rustc_codegen_ssa[17da4602a24c95d7]::traits::backend::CodegenBackend>::codegen_crate
21: 0x7faa9bc618a3 - <rustc_session[3020f9ab2aa3381d]::session::Session>::time::<alloc[6c5b139dcb2c8bbf]::boxed::Box<dyn core[ba37dea25f4d3bf]::any::Any>, rustc_interface[c2fe7fc1c7f3d840]::passes::start_codegen::{closure#0}>
22: 0x7faa9bcda22d - rustc_interface[c2fe7fc1c7f3d840]::passes::start_codegen
23: 0x7faa9bc86a41 - <rustc_middle[390b614d8a4d42db]::ty::context::GlobalCtxt>::enter::<<rustc_interface[c2fe7fc1c7f3d840]::queries::Queries>::ongoing_codegen::{closure#0}, core[ba37dea25f4d3bf]::result::Result<alloc[6c5b139dcb2c8bbf]::boxed::Box<dyn core[ba37dea25f4d3bf]::any::Any>, rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>>
24: 0x7faa9bc603a6 - <rustc_interface[c2fe7fc1c7f3d840]::queries::Queries>::ongoing_codegen
25: 0x7faa9baea458 - <rustc_interface[c2fe7fc1c7f3d840]::interface::Compiler>::enter::<rustc_driver_impl[c8b89ddd6b0e3357]::run_compiler::{closure#1}::{closure#2}, core[ba37dea25f4d3bf]::result::Result<core[ba37dea25f4d3bf]::option::Option<rustc_interface[c2fe7fc1c7f3d840]::queries::Linker>, rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>>
26: 0x7faa9bb19b01 - rustc_span[927b0836cec2b3bf]::set_source_map::<core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>, rustc_interface[c2fe7fc1c7f3d840]::interface::run_compiler<core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>, rustc_driver_impl[c8b89ddd6b0e3357]::run_compiler::{closure#1}>::{closure#0}::{closure#0}>
27: 0x7faa9bb206c3 - <scoped_tls[9d8b08ed86df779e]::ScopedKey<rustc_span[927b0836cec2b3bf]::SessionGlobals>>::set::<rustc_interface[c2fe7fc1c7f3d840]::interface::run_compiler<core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>, rustc_driver_impl[c8b89ddd6b0e3357]::run_compiler::{closure#1}>::{closure#0}, core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>>
28: 0x7faa9bb02229 - std[912838f7fde4888d]::sys_common::backtrace::__rust_begin_short_backtrace::<rustc_interface[c2fe7fc1c7f3d840]::util::run_in_thread_pool_with_globals<rustc_interface[c2fe7fc1c7f3d840]::interface::run_compiler<core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>, rustc_driver_impl[c8b89ddd6b0e3357]::run_compiler::{closure#1}>::{closure#0}, core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>>
29: 0x7faa9bb02f01 - <<std[912838f7fde4888d]::thread::Builder>::spawn_unchecked_<rustc_interface[c2fe7fc1c7f3d840]::util::run_in_thread_pool_with_globals<rustc_interface[c2fe7fc1c7f3d840]::interface::run_compiler<core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>, rustc_driver_impl[c8b89ddd6b0e3357]::run_compiler::{closure#1}>::{closure#0}, core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>>::{closure#0}::{closure#0}, core[ba37dea25f4d3bf]::result::Result<(), rustc_span[927b0836cec2b3bf]::ErrorGuaranteed>>::{closure#1} as core[ba37dea25f4d3bf]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
30: 0x7faa9acde065 - std::sys::unix::thread::Thread::new::thread_start::hc275d8893483110f
31: 0x7faa9aa8f6ba - start_thread
at ./nptl/pthread_create.c:444:8
32: 0x7faa9ab1e0d0 - __GI___clone3
at ./misc/../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
33: 0x0 - <unknown>
error: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md
note: rustc 1.72.0-nightly (1fd11f948 2023-08-23) (1.72.0.0) running on x86_64-unknown-linux-gnu
note: compiler flags: --crate-type bin -C embed-bitcode=no -C debuginfo=2 -C incremental=[REDACTED] -Z unstable-options -C link-arg=-Tlinkall.x -C link-arg=-nostartfiles -C link-arg=-Trom_functions.x
note: some of the compiler flags provided by cargo are hidden
query stack during panic:
end of query stack
I seem unable to parse a request body for a post.
I have created a type modeling my request, and created a custom FromRequest implementation, but can't figure out what kind of signature my handler function should have.
Can you perhaps add an example for this?
Hello,
I'm currently developing a captive portal for a Pico W using embassy
. It works when I hardcode responses for each device:
fn make_app() -> picoserve::Router<AppRouter> {
picoserve::Router::new()
.route(
"/generate_204",
get(|| picoserve::response::File::html(include_str!("connect.html"))),
)
.route(
"/hotspot-detect.html",
get(|| picoserve::response::File::html(include_str!("connect.html"))),
)
.route(
"/connecttest.txt",
get(|| picoserve::response::File::html(include_str!("connect.html"))),
)
.route(
"/redirect",
get(|| picoserve::response::File::html(include_str!("connect.html"))),
)
}
However, I can't figure out how to implement a catch-all route as to support any device.
Am I missing something, or is this just not yet implemented?
Thanks :)
This allows either client or server send requests/responses without having to specify a Content-Length
upfront.
This works by specifying a Transfer-Encoding of chunked
, then the sender can send chunks of data and terminate the transfer by sending an empty chunk.
I'm particularly interested in chunked responses.
My specific use case for this is streaming JPEGs from an ESP32-S3.
When the ESP32-S3 is pulling JPEGs from a camera, it (usually) does not know the size of the image until the entire image is received into memory. Receiving the entire image into memory creates two problems; The first is latency, the image data should ideally be sent out ASAP after it's gotten from the camera. The second is image size, if the quality and width/height are large enough, the chip will be unable to fit the image in memory, which means it simply can't stream the image. Ideally the chip should be able to send out the image data ASAP to reduce the memory requirements.
I'm not sure what this should look like yet. Perhaps some kind of ChunkWriter
I guess but I'm not sure how the terminating chunk should be handled yet.
(This library is fantastic btw! Thanks for building iand sharing it!)
Has anyone tried doing HTTPS?
Now this may not be a problem with this library itself but is anyone experiencing infinite loading times in browsers (both Firefox and Chromium based) when it is deployed on the Pi Pico W?
Given the following code:
#[embassy_executor::task]
async fn web_task(
stack: &'static embassy_net::Stack<cyw43::NetDriver<'static>>,
app: &'static picoserve::Router<AppRouter>,
config: &'static picoserve::Config<Duration>,
) -> ! {
let mut rx_buffer = [0; 1024];
let mut tx_buffer = [0; 1024];
loop {
let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
info!("Listening on TCP:80...");
if let Err(e) = socket.accept(80).await {
warn!("accept error: {:?}", e);
continue;
}
info!("Received connection from {:?}", socket.remote_endpoint());
let (socket_rx, socket_tx) = socket.split();
match picoserve::serve(app, EmbassyTimer, config, &mut [0; 2048], socket_rx, socket_tx).await {
Ok(handled_requests_count) => {
info!(
"{} requests handled from {:?}",
handled_requests_count,
socket.remote_endpoint()
);
}
Err(_) => error!("an error occurred"),
}
}
}
async fn get_root() -> impl IntoResponse {
Response::new(OK, "hello world!").with_headers(("Connection", "close"))
}
fn make_app() -> picoserve::Router<AppRouter> {
picoserve::Router::new()
.route("/", get(get_root))
}
The log message shows:
10.986010 INFO Received connection from Some(192.168.1.6:50968)
└─ wifi_tcp_server::__web_task_task::{async_fn#0} @ src/bin/wifi_tcp_server.rs:86
15.986416 INFO 0 requests handled from Some(192.168.1.6:50968)
└─ wifi_tcp_server::__web_task_task::{async_fn#0} @ src/bin/wifi_tcp_server.rs:92
15.986596 INFO Listening on TCP:80...
└─ wifi_tcp_server::__web_task_task::{async_fn#0} @ src/bin/wifi_tcp_server.rs:80
If I make the same request with any other HTTP client (e.g., curl) it successfully returns the result perfectly fine and almost immediately. This makes me think that the browser doesn't know when to stop reading the HTTP response or something.
The same server running on the computer works in the browser.
Debugging the problem with Wireshark
Expected (using curl):
[SYN] Seq=0 Win=32120 Len=0 MSS=1460 SACK_PERM=1 TSval=647729354 TSecr=0 WS=128
[SYN, ACK] Seq=0 Ack=1 Win=1024 Len=0 MSS=1460 WS=1 SACK_PERM=1
[ACK] Seq=1 Ack=1 Win=32128 Len=0
GET / HTTP/1.1
HTTP/1.1 200
[ACK] Seq=77 Ack=77 Win=32128 Len=0
[FIN, ACK] Seq=77 Ack=77 Win=32128 Len=0
[ACK] Seq=77 Ack=78 Win=1024 Len=0
Found (using Firefox):
[SYN] Seq=0 Win=32120 Len=0 MSS=1460 SACK_PERM=1 TSval=646394589 TSecr=0 WS=128
[SYN] Seq=0 Win=32120 Len=0 MSS=1460 SACK_PERM=1 TSval=646394840 TSecr=0 WS=128
[RST, ACK] Seq=1 Ack=1 Win=0 Len=0
[SYN] Seq=0 Win=32120 Len=0 MSS=1460 SACK_PERM=1 TSval=646394856 TSecr=0 WS=128
[RST, ACK] Seq=1 Ack=1 Win=0 Len=0
[SYN, ACK] Seq=0 Ack=1 Win=1024 Len=0 MSS=1460 WS=1 SACK_PERM=1
[ACK] Seq=1 Ack=1 Win=32128 Len=0
Hello,
I'd like to run a tile server on my esp32, serving tiles off of an SD card. This involves many thousands of files, and I cannot load all the file names into RAM. Instead, whenever I get a request, I'd like to check the SD card for the requested file and send it.
I believe this is doable using a custom type implementing PathRouter
, and wrapping it in a Router
. Unfortunately, I can't actually find a way to do this. It doesn't seem to be possible to instantiate a Router<T>
, where T
is my own type.
Anything I'm overlooking here? Otherwise I'd be happy to submit a PR to allow this.
Thanks!
It can be useful to accept JSON in request.
Here is a minimal implementation using serde_json_core
use serde_json_core::de::Error as JsonError;
pub struct JsonRejection(JsonError);
impl IntoResponse for JsonRejection {
async fn write_to<W: picoserve::response::ResponseWriter>(
self,
response_writer: W,
) -> Result<picoserve::ResponseSent, W::Error> {
(
picoserve::response::status::BAD_REQUEST,
format_args!("Request Body is not valid JSON: {}", self.0),
)
.write_to(response_writer)
.await
}
}
pub struct Json<T: serde::de::DeserializeOwned>(pub T);
impl<State, T: serde::de::DeserializeOwned> FromRequest<State> for Json<T> {
type Rejection = JsonRejection;
async fn from_request(
_state: &State,
request: &Request<'_>,
) -> Result<Json<T>, JsonRejection> {
serde_json_core::from_slice(request.body)
.map(|(v, _)| Self(v))
.map_err(JsonRejection)
}
}
I saw you bumped the version of picoserve to 0.10.0 yesterday and marked a release date in the changelog. However, I cannot find the new version on crates.io. Is that intentional or an oversight?
The header parsing code panics with an overflow error if a header value contains a percent sign followed by anything other than the characters A
to F
.
e.g., X-Foobar: abc%20def
The issue appears to be this code:
Lines 670 to 675 in ebc1002
Subtracting b'A'
causes an overflow if the value is anything other than A
through F
. Presumably the code should subtract b'0'
if the character is 0
through 9
.
However, it's not clear to me that it is correct for this code to try and attempt percent decoding in the first place. HTTP header fields are not percent encoded according to RFC 7230. It specifies percent encoding for URI values only. With regards to header field parsing, section 3.2.4 states:
Historically, HTTP has allowed field content with text in the ISO-8859-1 charset [ISO-8859-1], supporting other charsets only through use of [RFC2047] encoding. In practice, most HTTP header field values use only a subset of the US-ASCII charset [USASCII]. Newly defined header fields SHOULD limit their field values to US-ASCII octets. A recipient SHOULD treat other octets in field content (obs-text) as opaque data.
RFC 8187 does specify a more complex encoding scheme to support non-ASCII characters in headers that use "header: token; name=value"
parameter syntax, and it uses percent encoding as part of this. However, this doesn't appear to apply generally to all headers.
For whatever reason, my client (a WASM app that use gloo-net to send the query) add an extra &
at the end of the query.
The makes the parsing of query parameters fails with a Bad Query
error.
The error originate at
Line 473 in e5086aa
I think we could be more forgiving and ignore empty URL segments at least at the end of the URL.
Hello, is there any way to express that I want to borrow from request body in FromRequest
trait implementation? For example, getting &str
instead of heapless::String
in example.
Hello,
we use picoserve for updating firmware and configurations on embedded devices. Our updates are large, and storing the entire body in RAM is resource-intensive.
We suggest enhancing request.body()
to implement io::Read
instead of holding the entire payload in memory. Our devices support async writes, and streaming the body directly would greatly reduce RAM usage, benefiting not only us but anyone with similar embedded device constraints.
This change would streamline operations for large payloads, making picoserve more efficient for embedded systems.
Hello, currently signature of Content::write_content
suggests, that no other error could be return other than from writer, but I would like to return other errors. In my use case I'm reading from filesystem and it can give an error.
Is websocket expected to work on embassy? I tried to set them up working from https://github.com/sammhicks/picoserve/blob/main/examples/web_sockets/src/main.rs, and the requests just fail on the browser side. I'm not sure why that would be. Other requests for /
and others work fine, but when I try to create a new WebSocket()
in the js, the GET
request never even shows up on the microcontroller.
The very reduced route for /ws
just to see if the GET
request is even coming in.
.route("/ws", get(|| async move {
println!("requested websocet");
"stuff"
}));
The WebSocket constructor in js. This is run from the index.html|js that are hosted on the microcontroller.
let ws = new WebSocket("ws://" + window.location.host + "/ws", ["messages"]);
Chrome console error message
index.js:66 WebSocket connection to 'ws://test-hostname.lan:8000/ws' failed:
Firefox console error message
Firefox can’t establish a connection to the server at ws://test-hostname:8000/ws
I am trying to compile the latest git commit (d71c934) with alloc, defmt and embassy features enabled. However, it fails to compile with the following errors:
error[E0277]: the trait bound `AcceptError: Format` is not satisfied
--> /usr/local/share/rust/git/checkouts/picoserve-521def08cc55ec79/d71c934/src/logging.rs:26:13
|
26 | defmt::warn!($f $(,$arg)*);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Format` is not implemented for `AcceptError`
|
::: /usr/local/share/rust/git/checkouts/picoserve-521def08cc55ec79/d71c934/src/lib.rs:384:13
|
384 | log_warn!("{}: accept error: {:?}", task_id, err);
| ------------------------------------------------- in this macro invocation
|
= help: the following other types implement trait `Format`:
bool
char
isize
i8
i16
i32
i64
i128
and 163 others
note: required by a bound in `defmt::export::fmt`
--> /usr/local/share/rust/registry/src/github.com-1ecc6299db9ec823/defmt-0.3.6/src/export/mod.rs:137:15
|
137 | pub fn fmt<T: Format + ?Sized>(f: &T) {
| ^^^^^^ required by this bound in `fmt`
= note: this error originates in the macro `defmt::warn` which comes from the expansion of the macro `log_warn` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `IpEndpoint: Format` is not satisfied
--> /usr/local/share/rust/git/checkouts/picoserve-521def08cc55ec79/d71c934/src/logging.rs:42:13
|
42 | defmt::info!($f $(,$arg)*);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Format` is not implemented for `IpEndpoint`, which is required by `core::option::Option<IpEndpoint>: Format`
|
::: /usr/local/share/rust/git/checkouts/picoserve-521def08cc55ec79/d71c934/src/lib.rs:390:9
|
390 | / log_info!(
391 | | "{}: Received connection from {:?}",
392 | | task_id,
393 | | remote_endpoint
394 | | );
| |_________- in this macro invocation
|
= help: the following other types implement trait `Format`:
bool
char
isize
i8
i16
i32
i64
i128
and 163 others
= note: required for `core::option::Option<IpEndpoint>` to implement `Format`
note: required by a bound in `defmt::export::fmt`
--> /usr/local/share/rust/registry/src/github.com-1ecc6299db9ec823/defmt-0.3.6/src/export/mod.rs:137:15
|
137 | pub fn fmt<T: Format + ?Sized>(f: &T) {
| ^^^^^^ required by this bound in `fmt`
= note: this error originates in the macro `defmt::info` which comes from the expansion of the macro `log_info` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `IpEndpoint: Format` is not satisfied
--> /usr/local/share/rust/git/checkouts/picoserve-521def08cc55ec79/d71c934/src/logging.rs:42:13
|
42 | defmt::info!($f $(,$arg)*);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Format` is not implemented for `IpEndpoint`, which is required by `core::option::Option<IpEndpoint>: Format`
|
::: /usr/local/share/rust/git/checkouts/picoserve-521def08cc55ec79/d71c934/src/lib.rs:398:17
|
398 | / log_info!(
399 | | "{} requests handled from {:?}",
400 | | handled_requests_count,
401 | | remote_endpoint
402 | | );
| |_________________- in this macro invocation
|
= help: the following other types implement trait `Format`:
bool
char
isize
i8
i16
i32
i64
i128
and 163 others
= note: required for `core::option::Option<IpEndpoint>` to implement `Format`
note: required by a bound in `defmt::export::fmt`
--> /usr/local/share/rust/registry/src/github.com-1ecc6299db9ec823/defmt-0.3.6/src/export/mod.rs:137:15
|
137 | pub fn fmt<T: Format + ?Sized>(f: &T) {
| ^^^^^^ required by this bound in `fmt`
= note: this error originates in the macro `defmt::info` which comes from the expansion of the macro `log_info` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `<R as ErrorType>::Error: Format` is not satisfied
--> /usr/local/share/rust/git/checkouts/picoserve-521def08cc55ec79/d71c934/src/logging.rs:10:13
|
10 | defmt::error!($f $(,$arg)*);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Format` is not implemented for `<R as ErrorType>::Error`, which is required by `ReadExactError<<R as ErrorType>::Error>: Format`
|
::: /usr/local/share/rust/git/checkouts/picoserve-521def08cc55ec79/d71c934/src/extract.rs:199:17
|
199 | log_error!("Failed to read body: {:?}", err);
| -------------------------------------------- in this macro invocation
|
= note: required for `ReadExactError<<R as ErrorType>::Error>` to implement `Format`
note: required by a bound in `defmt::export::fmt`
--> /usr/local/share/rust/registry/src/github.com-1ecc6299db9ec823/defmt-0.3.6/src/export/mod.rs:137:15
|
137 | pub fn fmt<T: Format + ?Sized>(f: &T) {
| ^^^^^^ required by this bound in `fmt`
= note: this error originates in the macro `defmt::error` which comes from the expansion of the macro `log_error` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider further restricting the associated type
--> /usr/local/share/rust/git/checkouts/picoserve-521def08cc55ec79/d71c934/src/extract.rs:182:39
|
182 | ) -> Result<Self, Self::Rejection> where <R as ErrorType>::Error: Format {
| +++++++++++++++++++++++++++++++++++++
For more information about this error, try `rustc --explain E0277`.
error: could not compile `picoserve` (lib) due to 4 previous errors
This is a very cool project. I'd like to use it on my embassy project, but I'm restricted to the stable toolchain.
Looking at the docu and the examples, it looks like this is not possible, as this crate relies on the #![feature(type_alias_impl_trait)]
feature (which doesn't look like it will be stabilized soon).
Hello, as post_service
and similar require path specifiers, NoPathParameters
and others should probably not be #[doc(hidden)]
anymore. It is not a big deal but rust-analyzer is not doing auto imports with that.
Hi, I've spent some effort on getting picoserve to work on an esp32, nostd + embassy.
I've got it to work, but I needed to patch the network crate (esp-wifi) as it relies on the embassy crates published to crates.io, and picoserve relies on a git dependency. Embassy does not seem to release their crates to crates.io a lot.
As far as you know, Is there a specific feature picoserve needs from the more recent embassy dependencies?
This allows to use your structs with the log macros from defmt
, which is the suggested logging framework with embassy.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.