Repository: bytecodealliance/wasmtime
Pull request #7529 (2023-11-12 20:54:18)

[15.0.0] Backport WIT-changing PRs
This is a backport of a number of PRs to `main` all of which changes WASI WIT files:

* https://github.com/bytecodealliance/wasmtime/pull/7515
* https://github.com/bytecodealliance/wasmtime/pull/7521
* https://github.com/bytecodealliance/wasmtime/pull/7524
* https://github.com/bytecodealliance/wasmtime/pull/7434
* https://github.com/bytecodealliance/wasmtime/pull/7528
* https://github.com/bytecodealliance/wasmtime/pull/7516

After this PR the `crates/wasi/wit` directory is the same on `release-15.0.0` and `main`


Patch:
diff --git a/crates/wasi-http/src/body.rs b/crates/wasi-http/src/body.rs
index 03cc695bc..c4ada9ef9 100644
--- a/crates/wasi-http/src/body.rs
+++ b/crates/wasi-http/src/body.rs
@@ -14,7 +14,7 @@ use wasmtime_wasi::preview2::{
     Subscribe,
 };
 
-pub type HyperIncomingBody = BoxBody<Bytes, types::Error>;
+pub type HyperIncomingBody = BoxBody<Bytes, types::ErrorCode>;
 
 /// Small wrapper around `BoxBody` which adds a timeout to every frame.
 struct BodyWithTimeout {
@@ -45,12 +45,12 @@ impl BodyWithTimeout {
 
 impl Body for BodyWithTimeout {
     type Data = Bytes;
-    type Error = types::Error;
+    type Error = types::ErrorCode;
 
     fn poll_frame(
         self: Pin<&mut Self>,
         cx: &mut Context<'_>,
-    ) -> Poll<Option<Result<Frame<Bytes>, types::Error>>> {
+    ) -> Poll<Option<Result<Frame<Bytes>, types::ErrorCode>>> {
         let me = Pin::into_inner(self);
 
         // If the timeout timer needs to be reset, do that now relative to the
@@ -66,9 +66,7 @@ impl Body for BodyWithTimeout {
         // Register interest in this context on the sleep timer, and if the
         // sleep elapsed that means that we've timed out.
         if let Poll::Ready(()) = me.timeout.as_mut().poll(cx) {
-            return Poll::Ready(Some(Err(types::Error::TimeoutError(
-                "frame timed out".to_string(),
-            ))));
+            return Poll::Ready(Some(Err(types::ErrorCode::ConnectionReadTimeout)));
         }
 
         // Without timeout business now handled check for the frame. If a frame
@@ -222,7 +220,7 @@ impl Subscribe for HostIncomingBodyStream {
 }
 
 impl HostIncomingBodyStream {
-    fn record_frame(&mut self, frame: Option<Result<Frame<Bytes>, types::Error>>) {
+    fn record_frame(&mut self, frame: Option<Result<Frame<Bytes>, types::ErrorCode>>) {
         match frame {
             Some(Ok(frame)) => match frame.into_data() {
                 // A data frame was received, so queue up the buffered data for
@@ -300,7 +298,7 @@ pub enum HostFutureTrailers {
     ///
     /// Note that `Ok(None)` means that there were no trailers for this request
     /// while `Ok(Some(_))` means that trailers were found in the request.
-    Done(Result<Option<FieldMap>, types::Error>),
+    Done(Result<Option<FieldMap>, types::ErrorCode>),
 }
 
 #[async_trait::async_trait]
@@ -328,9 +326,7 @@ impl Subscribe for HostFutureTrailers {
                 // this just in case though.
                 Err(_) => {
                     debug_assert!(false, "should be unreachable");
-                    *self = HostFutureTrailers::Done(Err(types::Error::ProtocolError(
-                        "stream hung up before trailers were received".to_string(),
-                    )));
+                    *self = HostFutureTrailers::Done(Err(types::ErrorCode::ConnectionTerminated));
                 }
             }
         }
@@ -362,7 +358,7 @@ impl Subscribe for HostFutureTrailers {
     }
 }
 
-pub type HyperOutgoingBody = BoxBody<Bytes, types::Error>;
+pub type HyperOutgoingBody = BoxBody<Bytes, types::ErrorCode>;
 
 pub enum FinishMessage {
     Finished,
@@ -384,7 +380,7 @@ impl HostOutgoingBody {
         }
         impl Body for BodyImpl {
             type Data = Bytes;
-            type Error = types::Error;
+            type Error = types::ErrorCode;
             fn poll_frame(
                 mut self: Pin<&mut Self>,
                 cx: &mut Context<'_>,
@@ -406,9 +402,9 @@ impl HostOutgoingBody {
                                     FinishMessage::Trailers(trailers) => {
                                         Poll::Ready(Some(Ok(Frame::trailers(trailers))))
                                     }
-                                    FinishMessage::Abort => Poll::Ready(Some(Err(
-                                        types::Error::ProtocolError("response corrupted".into()),
-                                    ))),
+                                    FinishMessage::Abort => {
+                                        Poll::Ready(Some(Err(types::ErrorCode::HttpProtocolError)))
+                                    }
                                 },
                                 Poll::Ready(Err(RecvError { .. })) => Poll::Ready(None),
                             }
diff --git a/crates/wasi-http/src/http_impl.rs b/crates/wasi-http/src/http_impl.rs
index 47ff08930..7e5594712 100644
--- a/crates/wasi-http/src/http_impl.rs
+++ b/crates/wasi-http/src/http_impl.rs
@@ -1,22 +1,22 @@
-use crate::bindings::http::{
-    outgoing_handler,
-    types::{self as http_types, Scheme},
+use crate::{
+    bindings::http::{
+        outgoing_handler,
+        types::{self, Scheme},
+    },
+    types::{HostFutureIncomingResponse, HostOutgoingRequest, OutgoingRequest},
+    WasiHttpView,
 };
-use crate::types::{self, HostFutureIncomingResponse, OutgoingRequest};
-use crate::WasiHttpView;
 use bytes::Bytes;
 use http_body_util::{BodyExt, Empty};
 use hyper::Method;
-use types::HostOutgoingRequest;
 use wasmtime::component::Resource;
 
 impl<T: WasiHttpView> outgoing_handler::Host for T {
     fn handle(
         &mut self,
         request_id: Resource<HostOutgoingRequest>,
-        options: Option<Resource<http_types::RequestOptions>>,
-    ) -> wasmtime::Result<Result<Resource<HostFutureIncomingResponse>, outgoing_handler::Error>>
-    {
+        options: Option<Resource<types::RequestOptions>>,
+    ) -> wasmtime::Result<Result<Resource<HostFutureIncomingResponse>, types::ErrorCode>> {
         let opts = options.and_then(|opts| self.table().get(&opts).ok());
 
         let connect_timeout = opts
@@ -32,32 +32,30 @@ impl<T: WasiHttpView> outgoing_handler::Host for T {
             .unwrap_or(std::time::Duration::from_millis(600 * 1000));
 
         let req = self.table().delete(request_id)?;
+        let mut builder = hyper::Request::builder();
 
-        let method = match req.method {
-            crate::bindings::http::types::Method::Get => Method::GET,
-            crate::bindings::http::types::Method::Head => Method::HEAD,
-            crate::bindings::http::types::Method::Post => Method::POST,
-            crate::bindings::http::types::Method::Put => Method::PUT,
-            crate::bindings::http::types::Method::Delete => Method::DELETE,
-            crate::bindings::http::types::Method::Connect => Method::CONNECT,
-            crate::bindings::http::types::Method::Options => Method::OPTIONS,
-            crate::bindings::http::types::Method::Trace => Method::TRACE,
-            crate::bindings::http::types::Method::Patch => Method::PATCH,
-            crate::bindings::http::types::Method::Other(method) => {
-                return Ok(Err(outgoing_handler::Error::InvalidUrl(format!(
-                    "unknown method {method}"
-                ))));
-            }
-        };
+        builder = builder.method(match req.method {
+            types::Method::Get => Method::GET,
+            types::Method::Head => Method::HEAD,
+            types::Method::Post => Method::POST,
+            types::Method::Put => Method::PUT,
+            types::Method::Delete => Method::DELETE,
+            types::Method::Connect => Method::CONNECT,
+            types::Method::Options => Method::OPTIONS,
+            types::Method::Trace => Method::TRACE,
+            types::Method::Patch => Method::PATCH,
+            types::Method::Other(m) => match hyper::Method::from_bytes(m.as_bytes()) {
+                Ok(method) => method,
+                Err(_) => return Ok(Err(types::ErrorCode::HttpRequestMethodInvalid)),
+            },
+        });
 
         let (use_tls, scheme, port) = match req.scheme.unwrap_or(Scheme::Https) {
-            Scheme::Http => (false, "http://", 80),
-            Scheme::Https => (true, "https://", 443),
-            Scheme::Other(scheme) => {
-                return Ok(Err(outgoing_handler::Error::InvalidUrl(format!(
-                    "unsupported scheme {scheme}"
-                ))))
-            }
+            Scheme::Http => (false, http::uri::Scheme::HTTP, 80),
+            Scheme::Https => (true, http::uri::Scheme::HTTPS, 443),
+
+            // We can only support http/https
+            Scheme::Other(_) => return Ok(Err(types::ErrorCode::HttpProtocolError)),
         };
 
         let authority = if let Some(authority) = req.authority {
@@ -69,14 +67,20 @@ impl<T: WasiHttpView> outgoing_handler::Host for T {
         } else {
             String::new()
         };
+        builder = builder.header(hyper::header::HOST, &authority);
+
+        let mut uri = http::Uri::builder()
+            .scheme(scheme)
+            .authority(authority.clone());
+
+        if let Some(path) = req.path_with_query {
+            uri = uri.path_and_query(path);
+        }
 
-        let mut builder = hyper::Request::builder()
-            .method(method)
-            .uri(format!(
-                "{scheme}{authority}{}",
-                req.path_with_query.as_ref().map_or("", String::as_ref)
-            ))
-            .header(hyper::header::HOST, &authority);
+        builder = builder.uri(
+            uri.build()
+                .map_err(|_| types::ErrorCode::HttpRequestUriInvalid)?,
+        );
 
         for (k, v) in req.headers.iter() {
             builder = builder.header(k, v);
@@ -84,9 +88,11 @@ impl<T: WasiHttpView> outgoing_handler::Host for T {
 
         let body = req
             .body
-            .unwrap_or_else(|| Empty::<Bytes>::new().map_err(|_| unreachable!()).boxed());
+            .unwrap_or_else(|| Empty::<Bytes>::new().map_err(|_| todo!("thing")).boxed());
 
-        let request = builder.body(body).map_err(types::http_protocol_error)?;
+        let request = builder
+            .body(body)
+            .map_err(|err| types::ErrorCode::InternalError(Some(err.to_string())))?;
 
         Ok(Ok(self.send_request(OutgoingRequest {
             use_tls,
diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs
index 936c48a1f..486c6b776 100644
--- a/crates/wasi-http/src/lib.rs
+++ b/crates/wasi-http/src/lib.rs
@@ -37,64 +37,9 @@ pub mod bindings {
     pub use wasi::http;
 }
 
-impl From<wasmtime_wasi::preview2::TableError> for crate::bindings::http::types::Error {
-    fn from(err: wasmtime_wasi::preview2::TableError) -> Self {
-        Self::UnexpectedError(err.to_string())
-    }
-}
-
-impl From<anyhow::Error> for crate::bindings::http::types::Error {
-    fn from(err: anyhow::Error) -> Self {
-        Self::UnexpectedError(err.to_string())
-    }
-}
-
-impl From<std::io::Error> for crate::bindings::http::types::Error {
-    fn from(err: std::io::Error) -> Self {
-        let message = err.to_string();
-        match err.kind() {
-            std::io::ErrorKind::InvalidInput => Self::InvalidUrl(message),
-            std::io::ErrorKind::AddrNotAvailable => Self::InvalidUrl(message),
-            _ => {
-                if message.starts_with("failed to lookup address information") {
-                    Self::InvalidUrl("invalid dnsname".to_string())
-                } else {
-                    Self::ProtocolError(message)
-                }
-            }
-        }
-    }
-}
-
-impl From<hyper::Error> for crate::bindings::http::types::Error {
-    fn from(err: hyper::Error) -> Self {
-        let message = err.message().to_string();
-        if err.is_timeout() {
-            Self::TimeoutError(message)
-        } else if err.is_parse_status() || err.is_user() {
-            Self::InvalidUrl(message)
-        } else if err.is_body_write_aborted()
-            || err.is_canceled()
-            || err.is_closed()
-            || err.is_incomplete_message()
-            || err.is_parse()
-        {
-            Self::ProtocolError(message)
-        } else {
-            Self::UnexpectedError(message)
-        }
-    }
-}
-
-impl From<tokio::time::error::Elapsed> for crate::bindings::http::types::Error {
-    fn from(err: tokio::time::error::Elapsed) -> Self {
-        Self::TimeoutError(err.to_string())
-    }
-}
-
-#[cfg(not(any(target_arch = "riscv64", target_arch = "s390x")))]
-impl From<rustls::client::InvalidDnsNameError> for crate::bindings::http::types::Error {
-    fn from(_err: rustls::client::InvalidDnsNameError) -> Self {
-        Self::InvalidUrl("invalid dnsname".to_string())
-    }
+pub(crate) fn dns_error(rcode: String, info_code: u16) -> bindings::http::types::ErrorCode {
+    bindings::http::types::ErrorCode::DnsError(bindings::http::types::DnsErrorPayload {
+        rcode: Some(rcode),
+        info_code: Some(info_code),
+    })
 }
diff --git a/crates/wasi-http/src/types.rs b/crates/wasi-http/src/types.rs
index c7041fcad..a5bc098da 100644
--- a/crates/wasi-http/src/types.rs
+++ b/crates/wasi-http/src/types.rs
@@ -4,6 +4,7 @@
 use crate::{
     bindings::http::types::{self, Method, Scheme},
     body::{HostIncomingBody, HyperIncomingBody, HyperOutgoingBody},
+    dns_error,
 };
 use http_body_util::BodyExt;
 use hyper::header::HeaderName;
@@ -50,7 +51,7 @@ pub trait WasiHttpView: Send {
     fn new_response_outparam(
         &mut self,
         result: tokio::sync::oneshot::Sender<
-            Result<hyper::Response<HyperOutgoingBody>, types::Error>,
+            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
         >,
     ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
         let id = self.table().push(HostResponseOutparam { result })?;
@@ -108,16 +109,30 @@ async fn handler(
     first_byte_timeout: Duration,
     request: http::Request<HyperOutgoingBody>,
     between_bytes_timeout: Duration,
-) -> Result<IncomingResponseInternal, types::Error> {
+) -> Result<IncomingResponseInternal, types::ErrorCode> {
     let tcp_stream = TcpStream::connect(authority.clone())
         .await
-        .map_err(invalid_url)?;
+        .map_err(|e| match e.kind() {
+            std::io::ErrorKind::AddrNotAvailable => {
+                dns_error("address not available".to_string(), 0)
+            }
+
+            _ => {
+                if e.to_string()
+                    .starts_with("failed to lookup address information")
+                {
+                    dns_error("address not available".to_string(), 0)
+                } else {
+                    types::ErrorCode::ConnectionRefused
+                }
+            }
+        })?;
 
     let (mut sender, worker) = if use_tls {
         #[cfg(any(target_arch = "riscv64", target_arch = "s390x"))]
         {
-            return Err(crate::bindings::http::types::Error::UnexpectedError(
-                "unsupported architecture for SSL".to_string(),
+            return Err(crate::bindings::http::types::ErrorCode::InternalError(
+                Some("unsupported architecture for SSL".to_string()),
             ));
         }
 
@@ -141,18 +156,20 @@ async fn handler(
             let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config));
             let mut parts = authority.split(":");
             let host = parts.next().unwrap_or(&authority);
-            let domain = rustls::ServerName::try_from(host)?;
+            let domain = rustls::ServerName::try_from(host)
+                .map_err(|_| dns_error("invalid dns name".to_string(), 0))?;
             let stream = connector
                 .connect(domain, tcp_stream)
                 .await
-                .map_err(|e| crate::bindings::http::types::Error::ProtocolError(e.to_string()))?;
+                .map_err(|_| types::ErrorCode::TlsProtocolError)?;
 
             let (sender, conn) = timeout(
                 connect_timeout,
                 hyper::client::conn::http1::handshake(stream),
             )
             .await
-            .map_err(|_| timeout_error("connection"))??;
+            .map_err(|_| types::ErrorCode::ConnectionTimeout)?
+            .map_err(|_| types::ErrorCode::ConnectionTimeout)?;
 
             let worker = preview2::spawn(async move {
                 match conn.await {
@@ -172,7 +189,8 @@ async fn handler(
             hyper::client::conn::http1::handshake(tcp_stream),
         )
         .await
-        .map_err(|_| timeout_error("connection"))??;
+        .map_err(|_| types::ErrorCode::ConnectionTimeout)?
+        .map_err(|_| types::ErrorCode::HttpProtocolError)?;
 
         let worker = preview2::spawn(async move {
             match conn.await {
@@ -187,9 +205,12 @@ async fn handler(
 
     let resp = timeout(first_byte_timeout, sender.send_request(request))
         .await
-        .map_err(|_| timeout_error("first byte"))?
-        .map_err(hyper_protocol_error)?
-        .map(|body| body.map_err(|e| e.into()).boxed());
+        .map_err(|_| types::ErrorCode::ConnectionReadTimeout)?
+        .map_err(|_| types::ErrorCode::HttpProtocolError)?
+        .map(|body| {
+            body.map_err(|_| types::ErrorCode::HttpProtocolError)
+                .boxed()
+        });
 
     Ok(IncomingResponseInternal {
         resp,
@@ -197,25 +218,6 @@ async fn handler(
         between_bytes_timeout,
     })
 }
-
-pub fn timeout_error(kind: &str) -> types::Error {
-    types::Error::TimeoutError(format!("{kind} timed out"))
-}
-
-pub fn http_protocol_error(e: http::Error) -> types::Error {
-    types::Error::ProtocolError(e.to_string())
-}
-
-pub fn hyper_protocol_error(e: hyper::Error) -> types::Error {
-    types::Error::ProtocolError(e.to_string())
-}
-
-fn invalid_url(e: std::io::Error) -> types::Error {
-    // TODO: DNS errors show up as a Custom io error, what subset of errors should we consider for
-    // InvalidUrl here?
-    types::Error::InvalidUrl(e.to_string())
-}
-
 impl From<http::Method> for types::Method {
     fn from(method: http::Method) -> Self {
         if method == http::Method::GET {
@@ -268,7 +270,7 @@ pub struct HostIncomingRequest {
 
 pub struct HostResponseOutparam {
     pub result:
-        tokio::sync::oneshot::Sender<Result<hyper::Response<HyperOutgoingBody>, types::Error>>,
+        tokio::sync::oneshot::Sender<Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>>,
 }
 
 pub struct HostOutgoingRequest {
@@ -347,11 +349,11 @@ pub struct IncomingResponseInternal {
 }
 
 type FutureIncomingResponseHandle =
-    AbortOnDropJoinHandle<anyhow::Result<Result<IncomingResponseInternal, types::Error>>>;
+    AbortOnDropJoinHandle<anyhow::Result<Result<IncomingResponseInternal, types::ErrorCode>>>;
 
 pub enum HostFutureIncomingResponse {
     Pending(FutureIncomingResponseHandle),
-    Ready(anyhow::Result<Result<IncomingResponseInternal, types::Error>>),
+    Ready(anyhow::Result<Result<IncomingResponseInternal, types::ErrorCode>>),
     Consumed,
 }
 
@@ -364,7 +366,9 @@ impl HostFutureIncomingResponse {
         matches!(self, Self::Ready(_))
     }
 
-    pub fn unwrap_ready(self) -> anyhow::Result<Result<IncomingResponseInternal, types::Error>> {
+    pub fn unwrap_ready(
+        self,
+    ) -> anyhow::Result<Result<IncomingResponseInternal, types::ErrorCode>> {
         match self {
             Self::Ready(res) => res,
             Self::Pending(_) | Self::Consumed => {
diff --git a/crates/wasi-http/src/types_impl.rs b/crates/wasi-http/src/types_impl.rs
index 89455e3aa..cf35b5b3b 100644
--- a/crates/wasi-http/src/types_impl.rs
+++ b/crates/wasi-http/src/types_impl.rs
@@ -1,5 +1,5 @@
 use crate::{
-    bindings::http::types::{self, Error, Headers, Method, Scheme, StatusCode, Trailers},
+    bindings::http::types::{self, Headers, Method, Scheme, StatusCode, Trailers},
     body::{FinishMessage, HostFutureTrailers, HostIncomingBody, HostOutgoingBody},
     types::{
         FieldMap, HostFields, HostFutureIncomingResponse, HostIncomingRequest,
@@ -17,7 +17,14 @@ use wasmtime_wasi::preview2::{
     Pollable, Table,
 };
 
-impl<T: WasiHttpView> crate::bindings::http::types::Host for T {}
+impl<T: WasiHttpView> crate::bindings::http::types::Host for T {
+    fn http_error_code(
+        &mut self,
+        _err: wasmtime::component::Resource<types::StreamError>,
+    ) -> wasmtime::Result<Option<types::ErrorCode>> {
+        todo!()
+    }
+}
 
 /// Take ownership of the underlying [`FieldMap`] associated with this fields resource. If the
 /// fields resource references another fields, the returned [`FieldMap`] will be cloned.
@@ -32,10 +39,10 @@ fn move_fields(table: &mut Table, id: Resource<HostFields>) -> wasmtime::Result<
     }
 }
 
-fn get_fields_mut<'a>(
+fn get_fields<'a>(
     table: &'a mut Table,
     id: &Resource<HostFields>,
-) -> wasmtime::Result<&'a mut FieldMap> {
+) -> wasmtime::Result<&'a FieldMap> {
     let fields = table.get(&id)?;
     if let HostFields::Ref { parent, get_fields } = *fields {
         let entry = table.get_any_mut(parent)?;
@@ -51,6 +58,16 @@ fn get_fields_mut<'a>(
     }
 }
 
+fn get_fields_mut<'a>(
+    table: &'a mut Table,
+    id: &Resource<HostFields>,
+) -> wasmtime::Result<Result<&'a mut FieldMap, types::HeaderError>> {
+    match table.get_mut(&id)? {
+        HostFields::Owned { fields } => Ok(Ok(fields)),
+        HostFields::Ref { .. } => Ok(Err(types::HeaderError::Immutable)),
+    }
+}
+
 fn is_forbidden_header<T: WasiHttpView>(view: &mut T, name: &HeaderName) -> bool {
     static FORBIDDEN_HEADERS: [HeaderName; 9] = [
         hyper::header::CONNECTION,
@@ -83,7 +100,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
         &mut self,
         entries: Vec<(String, Vec<u8>)>,
     ) -> wasmtime::Result<Result<Resource<HostFields>, types::HeaderError>> {
-        let mut map = hyper::HeaderMap::new();
+        let mut fields = hyper::HeaderMap::new();
 
         for (header, value) in entries {
             let header = match hyper::header::HeaderName::from_bytes(header.as_bytes()) {
@@ -100,12 +117,12 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
                 Err(_) => return Ok(Err(types::HeaderError::InvalidSyntax)),
             };
 
-            map.append(header, value);
+            fields.append(header, value);
         }
 
         let id = self
             .table()
-            .push(HostFields::Owned { fields: map })
+            .push(HostFields::Owned { fields })
             .context("[new_fields] pushing fields")?;
 
         Ok(Ok(id))
@@ -128,7 +145,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
             Err(_) => return Ok(vec![]),
         };
 
-        let res = get_fields_mut(self.table(), &fields)
+        let res = get_fields(self.table(), &fields)
             .context("[fields_get] getting fields")?
             .get_all(header)
             .into_iter()
@@ -160,14 +177,14 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
             }
         }
 
-        let m =
-            get_fields_mut(self.table(), &fields).context("[fields_set] getting mutable fields")?;
-        m.remove(&header);
-        for value in values {
-            m.append(&header, value);
-        }
-
-        Ok(Ok(()))
+        Ok(get_fields_mut(self.table(), &fields)
+            .context("[fields_set] getting mutable fields")?
+            .map(|fields| {
+                fields.remove(&header);
+                for value in values {
+                    fields.append(&header, value);
+                }
+            }))
     }
 
     fn delete(
@@ -184,9 +201,9 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
             return Ok(Err(types::HeaderError::Forbidden));
         }
 
-        let m = get_fields_mut(self.table(), &fields)?;
-        m.remove(header);
-        Ok(Ok(()))
+        Ok(get_fields_mut(self.table(), &fields)?.map(|fields| {
+            fields.remove(header);
+        }))
     }
 
     fn append(
@@ -209,27 +226,25 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
             Err(_) => return Ok(Err(types::HeaderError::InvalidSyntax)),
         };
 
-        let m = get_fields_mut(self.table(), &fields)
-            .context("[fields_append] getting mutable fields")?;
-
-        m.append(header, value);
-        Ok(Ok(()))
+        Ok(get_fields_mut(self.table(), &fields)
+            .context("[fields_append] getting mutable fields")?
+            .map(|fields| {
+                fields.append(header, value);
+            }))
     }
 
     fn entries(
         &mut self,
         fields: Resource<HostFields>,
     ) -> wasmtime::Result<Vec<(String, Vec<u8>)>> {
-        let fields = get_fields_mut(self.table(), &fields)?;
-        let result = fields
+        Ok(get_fields(self.table(), &fields)?
             .iter()
             .map(|(name, value)| (name.as_str().to_owned(), value.as_bytes().to_owned()))
-            .collect();
-        Ok(result)
+            .collect())
     }
 
     fn clone(&mut self, fields: Resource<HostFields>) -> wasmtime::Result<Resource<HostFields>> {
-        let fields = get_fields_mut(self.table(), &fields)
+        let fields = get_fields(self.table(), &fields)
             .context("[fields_clone] getting fields")?
             .clone();
 
@@ -517,7 +532,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostResponseOutparam for T {
     fn set(
         &mut self,
         id: Resource<HostResponseOutparam>,
-        resp: Result<Resource<HostOutgoingResponse>, Error>,
+        resp: Result<Resource<HostOutgoingResponse>, types::ErrorCode>,
     ) -> wasmtime::Result<()> {
         let val = match resp {
             Ok(resp) => Ok(self.table().delete(resp)?.try_into()?),
@@ -612,7 +627,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFutureTrailers for T {
     fn get(
         &mut self,
         id: Resource<HostFutureTrailers>,
-    ) -> wasmtime::Result<Option<Result<Option<Resource<Trailers>>, Error>>> {
+    ) -> wasmtime::Result<Option<Result<Option<Resource<Trailers>>, types::ErrorCode>>> {
         let trailers = self.table().get_mut(&id)?;
         match trailers {
             HostFutureTrailers::Waiting(_) => return Ok(None),
@@ -765,7 +780,9 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFutureIncomingResponse f
     fn get(
         &mut self,
         id: Resource<HostFutureIncomingResponse>,
-    ) -> wasmtime::Result<Option<Result<Result<Resource<HostIncomingResponse>, Error>, ()>>> {
+    ) -> wasmtime::Result<
+        Option<Result<Result<Resource<HostIncomingResponse>, types::ErrorCode>, ()>>,
+    > {
         let resp = self.table().get_mut(&id)?;
 
         match resp {
@@ -778,7 +795,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFutureIncomingResponse f
             match std::mem::replace(resp, HostFutureIncomingResponse::Consumed).unwrap_ready() {
                 Err(e) => {
                     // Trapping if it's not possible to downcast to an wasi-http error
-                    let e = e.downcast::<Error>()?;
+                    let e = e.downcast::<types::ErrorCode>()?;
                     return Ok(Some(Ok(Err(e))));
                 }
 
@@ -837,7 +854,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostOutgoingBody for T {
             .expect("outgoing-body trailer_sender consumed by a non-owning function");
 
         let message = if let Some(ts) = ts {
-            FinishMessage::Trailers(get_fields_mut(self.table(), &ts)?.clone().into())
+            FinishMessage::Trailers(move_fields(self.table(), ts)?)
         } else {
             FinishMessage::Finished
         };
diff --git a/crates/wasi-http/wit/deps/cli/reactor.wit b/crates/wasi-http/wit/deps/cli/reactor.wit
index 4b829834c..639f75557 100644
--- a/crates/wasi-http/wit/deps/cli/reactor.wit
+++ b/crates/wasi-http/wit/deps/cli/reactor.wit
@@ -3,7 +3,6 @@ package wasi:cli@0.2.0-rc-2023-11-05;
 world reactor {
   import wasi:clocks/wall-clock@0.2.0-rc-2023-11-05;
   import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/timezone@0.2.0-rc-2023-11-05;
   import wasi:filesystem/types@0.2.0-rc-2023-11-05;
   import wasi:filesystem/preopens@0.2.0-rc-2023-11-05;
   import wasi:sockets/instance-network@0.2.0-rc-2023-11-05;
diff --git a/crates/wasi-http/wit/deps/clocks/timezone.wit b/crates/wasi-http/wit/deps/clocks/timezone.wit
deleted file mode 100644
index e717e7b8d..000000000
--- a/crates/wasi-http/wit/deps/clocks/timezone.wit
+++ /dev/null
@@ -1,48 +0,0 @@
-interface timezone {
-    use wall-clock.{datetime};
-
-    /// Return information needed to display the given `datetime`. This includes
-    /// the UTC offset, the time zone name, and a flag indicating whether
-    /// daylight saving time is active.
-    ///
-    /// If the timezone cannot be determined for the given `datetime`, return a
-    /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight
-    /// saving time.
-    display: func(when: datetime) -> timezone-display;
-
-    /// The same as `display`, but only return the UTC offset.
-    utc-offset: func(when: datetime) -> s32;
-
-    /// Information useful for displaying the timezone of a specific `datetime`.
-    ///
-    /// This information may vary within a single `timezone` to reflect daylight
-    /// saving time adjustments.
-    record timezone-display {
-        /// The number of seconds difference between UTC time and the local
-        /// time of the timezone.
-        ///
-        /// The returned value will always be less than 86400 which is the
-        /// number of seconds in a day (24*60*60).
-        ///
-        /// In implementations that do not expose an actual time zone, this
-        /// should return 0.
-        utc-offset: s32,
-
-        /// The abbreviated name of the timezone to display to a user. The name
-        /// `UTC` indicates Coordinated Universal Time. Otherwise, this should
-        /// reference local standards for the name of the time zone.
-        ///
-        /// In implementations that do not expose an actual time zone, this
-        /// should be the string `UTC`.
-        ///
-        /// In time zones that do not have an applicable name, a formatted
-        /// representation of the UTC offset may be returned, such as `-04:00`.
-        name: string,
-
-        /// Whether daylight saving time is active.
-        ///
-        /// In implementations that do not expose an actual time zone, this
-        /// should return false.
-        in-daylight-saving-time: bool,
-    }
-}
diff --git a/crates/wasi-http/wit/deps/clocks/world.wit b/crates/wasi-http/wit/deps/clocks/world.wit
index a53d025b8..046145582 100644
--- a/crates/wasi-http/wit/deps/clocks/world.wit
+++ b/crates/wasi-http/wit/deps/clocks/world.wit
@@ -3,5 +3,4 @@ package wasi:clocks@0.2.0-rc-2023-11-05;
 world imports {
     import monotonic-clock;
     import wall-clock;
-    import timezone;
 }
diff --git a/crates/wasi-http/wit/deps/http/handler.wit b/crates/wasi-http/wit/deps/http/handler.wit
index 21b97a354..a34a0649d 100644
--- a/crates/wasi-http/wit/deps/http/handler.wit
+++ b/crates/wasi-http/wit/deps/http/handler.wit
@@ -22,7 +22,9 @@ interface incoming-handler {
 /// This interface defines a handler of outgoing HTTP Requests. It should be
 /// imported by components which wish to make HTTP Requests.
 interface outgoing-handler {
-  use types.{outgoing-request, request-options, future-incoming-response, error};
+  use types.{
+    outgoing-request, request-options, future-incoming-response, error-code
+  };
 
   /// This function is invoked with an outgoing HTTP Request, and it returns
   /// a resource `future-incoming-response` which represents an HTTP Response
@@ -37,5 +39,5 @@ interface outgoing-handler {
   handle: func(
     request: outgoing-request,
     options: option<request-options>
-  ) -> result<future-incoming-response, error>;
+  ) -> result<future-incoming-response, error-code>;
 }
diff --git a/crates/wasi-http/wit/deps/http/proxy.wit b/crates/wasi-http/wit/deps/http/proxy.wit
index 3fa8b9c63..4bc4eb644 100644
--- a/crates/wasi-http/wit/deps/http/proxy.wit
+++ b/crates/wasi-http/wit/deps/http/proxy.wit
@@ -8,7 +8,6 @@ world proxy {
   /// HTTP proxies have access to time and randomness.
   import wasi:clocks/wall-clock@0.2.0-rc-2023-11-05;
   import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/timezone@0.2.0-rc-2023-11-05;
   import wasi:random/random@0.2.0-rc-2023-11-05;
 
   /// Proxies have standard output and error streams which are expected to
diff --git a/crates/wasi-http/wit/deps/http/types.wit b/crates/wasi-http/wit/deps/http/types.wit
index fac47f61e..41226894e 100644
--- a/crates/wasi-http/wit/deps/http/types.wit
+++ b/crates/wasi-http/wit/deps/http/types.wit
@@ -3,7 +3,7 @@
 /// their headers, trailers, and bodies.
 interface types {
   use wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05.{duration};
-  use wasi:io/streams@0.2.0-rc-2023-11-05.{input-stream, output-stream};
+  use wasi:io/streams@0.2.0-rc-2023-11-05.{input-stream, output-stream, error as stream-error};
   use wasi:io/poll@0.2.0-rc-2023-11-05.{pollable};
 
   /// This type corresponds to HTTP standard Methods.
@@ -27,21 +27,100 @@ interface types {
     other(string)
   }
 
-  /// TODO: perhaps better align with HTTP semantics?
-  /// This type enumerates the different kinds of errors that may occur when
-  /// initially returning a response.
-  variant error {
-    invalid-url(string),
-    timeout-error(string),
-    protocol-error(string),
-    unexpected-error(string)
+  /// These cases are inspired by the IANA HTTP Proxy Error Types:
+  ///   https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-http-proxy-error-types
+  variant error-code {
+    DNS-timeout,
+    DNS-error(DNS-error-payload),
+    destination-not-found,
+    destination-unavailable,
+    destination-IP-prohibited,
+    destination-IP-unroutable,
+    connection-refused,
+    connection-terminated,
+    connection-timeout,
+    connection-read-timeout,
+    connection-write-timeout,
+    connection-limit-reached,
+    TLS-protocol-error,
+    TLS-certificate-error,
+    TLS-alert-received(TLS-alert-received-payload),
+    HTTP-request-denied,
+    HTTP-request-length-required,
+    HTTP-request-body-size(option<u64>),
+    HTTP-request-method-invalid,
+    HTTP-request-URI-invalid,
+    HTTP-request-URI-too-long,
+    HTTP-request-header-section-size(option<u32>),
+    HTTP-request-header-size(option<field-size-payload>),
+    HTTP-request-trailer-section-size(option<u32>),
+    HTTP-request-trailer-size(field-size-payload),
+    HTTP-response-incomplete,
+    HTTP-response-header-section-size(option<u32>),
+    HTTP-response-header-size(field-size-payload),
+    HTTP-response-body-size(option<u64>),
+    HTTP-response-trailer-section-size(option<u32>),
+    HTTP-response-trailer-size(field-size-payload),
+    HTTP-response-transfer-coding(option<string>),
+    HTTP-response-content-coding(option<string>),
+    HTTP-response-timeout,
+    HTTP-upgrade-failed,
+    HTTP-protocol-error,
+    loop-detected,
+    configuration-error,
+    /// This is a catch-all error for anything that doesn't fit cleanly into a
+    /// more specific case. It also includes an optional string for an
+    /// unstructured description of the error. Users should not depend on the
+    /// string for diagnosing errors, as it's not required to be consistent
+    /// between implementations.
+    internal-error(option<string>)
+  }
+
+  /// Defines the case payload type for `DNS-error` above:
+  record DNS-error-payload {
+    rcode: option<string>,
+    info-code: option<u16>
+  }
+
+  /// Defines the case payload type for `TLS-alert-received` above:
+  record TLS-alert-received-payload {
+    alert-id: option<u8>,
+    alert-message: option<string>
+  }
+
+  /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above:
+  record field-size-payload {
+    field-name: option<string>,
+    field-size: option<u32>
   }
 
-  /// This tyep enumerates the different kinds of errors that may occur when
+  /// Attempts to extract a http-related `error` from the stream `error`
+  /// provided.
+  ///
+  /// Stream operations which return `stream-error::last-operation-failed` have
+  /// a payload with more information about the operation that failed. This
+  /// payload can be passed through to this function to see if there's
+  /// http-related information about the error to return.
+  ///
+  /// Note that this function is fallible because not all stream-related errors
+  /// are http-related errors.
+  http-error-code: func(err: borrow<stream-error>) -> option<error-code>;
+
+  /// This type enumerates the different kinds of errors that may occur when
   /// setting or appending to a `fields` resource.
   variant header-error {
+    /// This error indicates that a `field-key` or `field-value` was
+    /// syntactically invalid when used with an operation that sets headers in a
+    /// `fields`.
     invalid-syntax,
+
+    /// This error indicates that a forbidden `field-key` was used when trying
+    /// to set a header in a `fields`.
     forbidden,
+
+    /// This error indicates that the operation on the `fields` was not
+    /// permitted because the fields are immutable.
+    immutable,
   }
 
   /// Field keys are always strings.
@@ -82,21 +161,14 @@ interface types {
 
     /// Set all of the values for a key. Clears any existing values for that
     /// key, if they have been set.
-    ///
-    /// The operation can fail if the name or value arguments are invalid, or if
-    /// the name is forbidden.
     set: func(name: field-key, value: list<field-value>) -> result<_, header-error>;
 
     /// Delete all values for a key. Does nothing if no values for the key
-    /// exist. Returns and error if the `field-key` is syntactically invalid, or
-    /// if the `field-key` is forbidden.
+    /// exist.
     delete: func(name: field-key) -> result<_, header-error>;
 
     /// Append a value for a key. Does not change or delete any existing
     /// values for that key.
-    ///
-    /// The operation can fail if the name or value arguments are invalid, or if
-    /// the name is forbidden.
     append: func(name: field-key, value: field-value) -> result<_, header-error>;
 
     /// Retrieve the full set of keys and values in the Fields. Like the
@@ -149,7 +221,7 @@ interface types {
   resource outgoing-request {
 
     /// Construct a new `outgoing-request` with a default `method` of `GET`, and
-    /// default values for `path-with-query`, `scheme`, and `authority.
+    /// `none` values for `path-with-query`, `scheme`, and `authority`.
     ///
     /// * `headers` is the HTTP Headers for the Request.
     ///
@@ -262,7 +334,7 @@ interface types {
     /// implementation determine how to respond with an HTTP error response.
     set: static func(
       param: response-outparam,
-      response: result<outgoing-response, error>,
+      response: result<outgoing-response, error-code>,
     );
   }
 
@@ -337,7 +409,7 @@ interface types {
     /// as well as any trailers, were received successfully, or that an error
     /// occured receiving them. The optional `trailers` indicates whether or not
     /// trailers were present in the body.
-    get: func() -> option<result<option<trailers>, error>>;
+    get: func() -> option<result<option<trailers>, error-code>>;
   }
 
   /// Represents an outgoing HTTP Response.
@@ -433,7 +505,7 @@ interface types {
     /// occured. Errors may also occur while consuming the response body,
     /// but those will be reported by the `incoming-body` and its
     /// `output-stream` child.
-    get: func() -> option<result<result<incoming-response, error>>>;
+    get: func() -> option<result<result<incoming-response, error-code>>>;
 
   }
 }
diff --git a/crates/wasi-http/wit/deps/io/error.wit b/crates/wasi-http/wit/deps/io/error.wit
new file mode 100644
index 000000000..b45c594e6
--- /dev/null
+++ b/crates/wasi-http/wit/deps/io/error.wit
@@ -0,0 +1,34 @@
+package wasi:io@0.2.0-rc-2023-11-05;
+
+
+interface error {
+    /// A resource which represents some error information.
+    ///
+    /// The only method provided by this resource is `to-debug-string`,
+    /// which provides some human-readable information about the error.
+    ///
+    /// In the `wasi:io` package, this resource is returned through the
+    /// `wasi:io/streams/stream-error` type.
+    ///
+    /// To provide more specific error information, other interfaces may
+    /// provide functions to further "downcast" this error into more specific
+    /// error information. For example, `error`s returned in streams derived
+    /// from filesystem types to be described using the filesystem's own
+    /// error-code type, using the function
+    /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter
+    /// `borrow<error>` and returns
+    /// `option<wasi:filesystem/types/error-code>`.
+    ///
+    /// The set of functions which can "downcast" an `error` into a more
+    /// concrete type is open.
+    resource error {
+        /// Returns a string that is suitable to assist humans in debugging
+        /// this error.
+        ///
+        /// WARNING: The returned string should not be consumed mechanically!
+        /// It may change across platforms, hosts, or other implementation
+        /// details. Parsing this string is a major platform-compatibility
+        /// hazard.
+        to-debug-string: func() -> string;
+    }
+}
diff --git a/crates/wasi-http/wit/deps/io/streams.wit b/crates/wasi-http/wit/deps/io/streams.wit
index 9442f60c0..45062505b 100644
--- a/crates/wasi-http/wit/deps/io/streams.wit
+++ b/crates/wasi-http/wit/deps/io/streams.wit
@@ -6,6 +6,7 @@ package wasi:io@0.2.0-rc-2023-11-05;
 /// In the future, the component model is expected to add built-in stream types;
 /// when it does, they are expected to subsume this API.
 interface streams {
+    use error.{error};
     use poll.{pollable};
 
     /// An error for input-stream and output-stream operations.
@@ -20,26 +21,6 @@ interface streams {
         closed
     }
 
-    /// Contextual error information about the last failure that happened on
-    /// a read, write, or flush from an `input-stream` or `output-stream`.
-    ///
-    /// This type is returned through the `stream-error` type whenever an
-    /// operation on a stream directly fails or an error is discovered
-    /// after-the-fact, for example when a write's failure shows up through a
-    /// later `flush` or `check-write`.
-    ///
-    /// Interfaces such as `wasi:filesystem/types` provide functionality to
-    /// further "downcast" this error into interface-specific error information.
-    resource error {
-        /// Returns a string that's suitable to assist humans in debugging this
-        /// error.
-        ///
-        /// The returned string will change across platforms and hosts which
-        /// means that parsing it, for example, would be a
-        /// platform-compatibility hazard.
-        to-debug-string: func() -> string;
-    }
-
     /// An input bytestream.
     ///
     /// `input-stream`s are *non-blocking* to the extent practical on underlying
@@ -51,21 +32,20 @@ interface streams {
     resource input-stream {
         /// Perform a non-blocking read from the stream.
         ///
-        /// This function returns a list of bytes containing the data that was
-        /// read, along with a `stream-status` which, indicates whether further
-        /// reads are expected to produce data. The returned list will contain up to
-        /// `len` bytes; it may return fewer than requested, but not more. An
-        /// empty list and `stream-status:open` indicates no more data is
-        /// available at this time, and that the pollable given by `subscribe`
-        /// will be ready when more data is available.
+        /// This function returns a list of bytes containing the read data,
+        /// when successful. The returned list will contain up to `len` bytes;
+        /// it may return fewer than requested, but not more. The list is
+        /// empty when no bytes are available for reading at this time. The
+        /// pollable given by `subscribe` will be ready when more bytes are
+        /// available.
         ///
-        /// Once a stream has reached the end, subsequent calls to `read` or
-        /// `skip` will always report `stream-status:ended` rather than producing more
-        /// data.
+        /// This function fails with a `stream-error` when the operation
+        /// encounters an error, giving `last-operation-failed`, or when the
+        /// stream is closed, giving `closed`.
         ///
-        /// When the caller gives a `len` of 0, it represents a request to read 0
-        /// bytes. This read should  always succeed and return an empty list and
-        /// the current `stream-status`.
+        /// When the caller gives a `len` of 0, it represents a request to
+        /// read 0 bytes. If the stream is still open, this call should
+        /// succeed and return an empty list, or otherwise fail with `closed`.
         ///
         /// The `len` parameter is a `u64`, which could represent a list of u8 which
         /// is not possible to allocate in wasm32, or not desirable to allocate as
@@ -77,24 +57,16 @@ interface streams {
         ) -> result<list<u8>, stream-error>;
 
         /// Read bytes from a stream, after blocking until at least one byte can
-        /// be read. Except for blocking, identical to `read`.
+        /// be read. Except for blocking, behavior is identical to `read`.
         blocking-read: func(
             /// The maximum number of bytes to read
             len: u64
         ) -> result<list<u8>, stream-error>;
 
-        /// Skip bytes from a stream.
-        ///
-        /// This is similar to the `read` function, but avoids copying the
-        /// bytes into the instance.
-        ///
-        /// Once a stream has reached the end, subsequent calls to read or
-        /// `skip` will always report end-of-stream rather than producing more
-        /// data.
+        /// Skip bytes from a stream. Returns number of bytes skipped.
         ///
-        /// This function returns the number of bytes skipped, along with a
-        /// `stream-status` indicating whether the end of the stream was
-        /// reached. The returned value will be at most `len`; it may be less.
+        /// Behaves identical to `read`, except instead of returning a list
+        /// of bytes, returns the number of bytes consumed from the stream.
         skip: func(
             /// The maximum number of bytes to skip.
             len: u64,
diff --git a/crates/wasi-http/wit/main.wit b/crates/wasi-http/wit/main.wit
deleted file mode 100644
index 90aa28ad3..000000000
--- a/crates/wasi-http/wit/main.wit
+++ /dev/null
@@ -1,33 +0,0 @@
-package wasmtime:wasi;
-
-// All of the same imports available in the wasi:cli/command world, but no
-// export required:
-world preview1-adapter-reactor {
-  import wasi:clocks/wall-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/timezone@0.2.0-rc-2023-11-05;
-  import wasi:filesystem/types@0.2.0-rc-2023-11-05;
-  import wasi:filesystem/preopens@0.2.0-rc-2023-11-05;
-  import wasi:sockets/instance-network@0.2.0-rc-2023-11-05;
-  import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-11-05;
-  import wasi:sockets/network@0.2.0-rc-2023-11-05;
-  import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-11-05;
-  import wasi:sockets/tcp@0.2.0-rc-2023-11-05;
-  import wasi:sockets/udp-create-socket@0.2.0-rc-2023-11-05;
-  import wasi:sockets/udp@0.2.0-rc-2023-11-05;
-  import wasi:random/random@0.2.0-rc-2023-11-05;
-  import wasi:random/insecure@0.2.0-rc-2023-11-05;
-  import wasi:random/insecure-seed@0.2.0-rc-2023-11-05;
-  import wasi:io/poll@0.2.0-rc-2023-11-05;
-  import wasi:io/streams@0.2.0-rc-2023-11-05;
-  import wasi:cli/environment@0.2.0-rc-2023-11-05;
-  import wasi:cli/exit@0.2.0-rc-2023-11-05;
-  import wasi:cli/stdin@0.2.0-rc-2023-11-05;
-  import wasi:cli/stdout@0.2.0-rc-2023-11-05;
-  import wasi:cli/stderr@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-input@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-output@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stdin@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stdout@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stderr@0.2.0-rc-2023-11-05;
-}
diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs
index ef42ac10c..3e2176516 100644
--- a/crates/wasi-preview1-component-adapter/src/lib.rs
+++ b/crates/wasi-preview1-component-adapter/src/lib.rs
@@ -45,7 +45,7 @@ pub mod bindings {
     #[cfg(feature = "reactor")]
     wit_bindgen::generate!({
         path: "../wasi/wit",
-        world: "wasmtime:wasi/preview1-adapter-reactor",
+        world: "wasi:cli/reactor",
         std_feature,
         raw_strings,
         // Automatically generated bindings for these functions will allocate
diff --git a/crates/wasi/src/preview2/command.rs b/crates/wasi/src/preview2/command.rs
index 898311157..6ff304b24 100644
--- a/crates/wasi/src/preview2/command.rs
+++ b/crates/wasi/src/preview2/command.rs
@@ -11,7 +11,6 @@ wasmtime::component::bindgen!({
        "wasi:clocks/monotonic_clock": crate::preview2::bindings::clocks::monotonic_clock,
        "wasi:io/poll": crate::preview2::bindings::io::poll,
        "wasi:io/streams": crate::preview2::bindings::io::streams,
-       "wasi:clocks/timezone": crate::preview2::bindings::clocks::timezone,
        "wasi:clocks/wall_clock": crate::preview2::bindings::clocks::wall_clock,
        "wasi:random/random": crate::preview2::bindings::random::random,
        "wasi:cli/environment": crate::preview2::bindings::cli::environment,
@@ -30,9 +29,9 @@ wasmtime::component::bindgen!({
 pub fn add_to_linker<T: WasiView>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()> {
     crate::preview2::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?;
     crate::preview2::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?;
-    crate::preview2::bindings::clocks::timezone::add_to_linker(l, |t| t)?;
     crate::preview2::bindings::filesystem::types::add_to_linker(l, |t| t)?;
     crate::preview2::bindings::filesystem::preopens::add_to_linker(l, |t| t)?;
+    crate::preview2::bindings::io::error::add_to_linker(l, |t| t)?;
     crate::preview2::bindings::io::poll::add_to_linker(l, |t| t)?;
     crate::preview2::bindings::io::streams::add_to_linker(l, |t| t)?;
     crate::preview2::bindings::random::random::add_to_linker(l, |t| t)?;
@@ -71,7 +70,6 @@ pub mod sync {
            "wasi:clocks/monotonic_clock": crate::preview2::bindings::clocks::monotonic_clock,
            "wasi:io/poll": crate::preview2::bindings::sync_io::io::poll,
            "wasi:io/streams": crate::preview2::bindings::sync_io::io::streams,
-           "wasi:clocks/timezone": crate::preview2::bindings::clocks::timezone,
            "wasi:clocks/wall_clock": crate::preview2::bindings::clocks::wall_clock,
            "wasi:random/random": crate::preview2::bindings::random::random,
            "wasi:cli/environment": crate::preview2::bindings::cli::environment,
@@ -92,9 +90,9 @@ pub mod sync {
     ) -> anyhow::Result<()> {
         crate::preview2::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?;
         crate::preview2::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?;
-        crate::preview2::bindings::clocks::timezone::add_to_linker(l, |t| t)?;
         crate::preview2::bindings::sync_io::filesystem::types::add_to_linker(l, |t| t)?;
         crate::preview2::bindings::filesystem::preopens::add_to_linker(l, |t| t)?;
+        crate::preview2::bindings::io::error::add_to_linker(l, |t| t)?;
         crate::preview2::bindings::sync_io::io::poll::add_to_linker(l, |t| t)?;
         crate::preview2::bindings::sync_io::io::streams::add_to_linker(l, |t| t)?;
         crate::preview2::bindings::random::random::add_to_linker(l, |t| t)?;
diff --git a/crates/wasi/src/preview2/host/clocks.rs b/crates/wasi/src/preview2/host/clocks.rs
index 0b4c7dd36..4467b3681 100644
--- a/crates/wasi/src/preview2/host/clocks.rs
+++ b/crates/wasi/src/preview2/host/clocks.rs
@@ -2,7 +2,6 @@
 
 use crate::preview2::bindings::{
     clocks::monotonic_clock::{self, Duration as WasiDuration, Instant},
-    clocks::timezone::{self, TimezoneDisplay},
     clocks::wall_clock::{self, Datetime},
 };
 use crate::preview2::poll::{subscribe, Subscribe};
@@ -102,13 +101,3 @@ impl Subscribe for Deadline {
         }
     }
 }
-
-impl<T: WasiView> timezone::Host for T {
-    fn display(&mut self, when: Datetime) -> anyhow::Result<TimezoneDisplay> {
-        todo!("timezone display is not implemented")
-    }
-
-    fn utc_offset(&mut self, when: Datetime) -> anyhow::Result<i32> {
-        todo!("timezone utc_offset is not implemented")
-    }
-}
diff --git a/crates/wasi/src/preview2/host/io.rs b/crates/wasi/src/preview2/host/io.rs
index 1ad5b4ccb..ca5bc44b8 100644
--- a/crates/wasi/src/preview2/host/io.rs
+++ b/crates/wasi/src/preview2/host/io.rs
@@ -1,10 +1,13 @@
 use crate::preview2::{
+    bindings::io::error,
     bindings::io::streams::{self, InputStream, OutputStream},
     poll::subscribe,
     Pollable, StreamError, StreamResult, WasiView,
 };
 use wasmtime::component::Resource;
 
+impl<T: WasiView> error::Host for T {}
+
 impl<T: WasiView> streams::Host for T {
     fn convert_stream_error(&mut self, err: StreamError) -> anyhow::Result<streams::StreamError> {
         match err {
@@ -17,7 +20,7 @@ impl<T: WasiView> streams::Host for T {
     }
 }
 
-impl<T: WasiView> streams::HostError for T {
+impl<T: WasiView> error::HostError for T {
     fn drop(&mut self, err: Resource<streams::Error>) -> anyhow::Result<()> {
         self.table_mut().delete(err)?;
         Ok(())
@@ -226,8 +229,8 @@ impl<T: WasiView> streams::HostInputStream for T {
 pub mod sync {
     use crate::preview2::{
         bindings::io::streams::{
-            self as async_streams, Host as AsyncHost, HostError as AsyncHostError,
-            HostInputStream as AsyncHostInputStream, HostOutputStream as AsyncHostOutputStream,
+            self as async_streams, Host as AsyncHost, HostInputStream as AsyncHostInputStream,
+            HostOutputStream as AsyncHostOutputStream,
         },
         bindings::sync_io::io::poll::Pollable,
         bindings::sync_io::io::streams::{self, InputStream, OutputStream},
@@ -253,16 +256,6 @@ pub mod sync {
         }
     }
 
-    impl<T: WasiView> streams::HostError for T {
-        fn drop(&mut self, err: Resource<streams::Error>) -> anyhow::Result<()> {
-            AsyncHostError::drop(self, err)
-        }
-
-        fn to_debug_string(&mut self, err: Resource<streams::Error>) -> anyhow::Result<String> {
-            AsyncHostError::to_debug_string(self, err)
-        }
-    }
-
     impl<T: WasiView> streams::HostOutputStream for T {
         fn drop(&mut self, stream: Resource<OutputStream>) -> anyhow::Result<()> {
             AsyncHostOutputStream::drop(self, stream)
diff --git a/crates/wasi/src/preview2/mod.rs b/crates/wasi/src/preview2/mod.rs
index 957a74a37..102d70fad 100644
--- a/crates/wasi/src/preview2/mod.rs
+++ b/crates/wasi/src/preview2/mod.rs
@@ -84,7 +84,7 @@ pub mod bindings {
                     "wasi:io/poll/pollable": super::super::io::poll::Pollable,
                     "wasi:io/streams/input-stream": super::super::io::streams::InputStream,
                     "wasi:io/streams/output-stream": super::super::io::streams::OutputStream,
-                    "wasi:io/streams/error": super::super::io::streams::Error,
+                    "wasi:io/error/error": super::super::io::error::Error,
                 }
             });
         }
@@ -169,7 +169,7 @@ pub mod bindings {
             "wasi:filesystem/types/descriptor": super::filesystem::Descriptor,
             "wasi:io/streams/input-stream": super::stream::InputStream,
             "wasi:io/streams/output-stream": super::stream::OutputStream,
-            "wasi:io/streams/error": super::stream::Error,
+            "wasi:io/error/error": super::stream::Error,
             "wasi:io/poll/pollable": super::poll::Pollable,
             "wasi:cli/terminal-input/terminal-input": super::stdio::TerminalInput,
             "wasi:cli/terminal-output/terminal-output": super::stdio::TerminalOutput,
diff --git a/crates/wasi/src/preview2/stream.rs b/crates/wasi/src/preview2/stream.rs
index 6200e1226..f81f5e277 100644
--- a/crates/wasi/src/preview2/stream.rs
+++ b/crates/wasi/src/preview2/stream.rs
@@ -32,7 +32,7 @@ pub trait HostInputStream: Subscribe {
     }
 }
 
-/// Representation of the `error` resource type in the `wasi:io/streams`
+/// Representation of the `error` resource type in the `wasi:io/error`
 /// interface.
 ///
 /// This is currently `anyhow::Error` to retain full type information for
diff --git a/crates/wasi/wit/deps/cli/reactor.wit b/crates/wasi/wit/deps/cli/reactor.wit
index 4b829834c..639f75557 100644
--- a/crates/wasi/wit/deps/cli/reactor.wit
+++ b/crates/wasi/wit/deps/cli/reactor.wit
@@ -3,7 +3,6 @@ package wasi:cli@0.2.0-rc-2023-11-05;
 world reactor {
   import wasi:clocks/wall-clock@0.2.0-rc-2023-11-05;
   import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/timezone@0.2.0-rc-2023-11-05;
   import wasi:filesystem/types@0.2.0-rc-2023-11-05;
   import wasi:filesystem/preopens@0.2.0-rc-2023-11-05;
   import wasi:sockets/instance-network@0.2.0-rc-2023-11-05;
diff --git a/crates/wasi/wit/deps/clocks/timezone.wit b/crates/wasi/wit/deps/clocks/timezone.wit
deleted file mode 100644
index e717e7b8d..000000000
--- a/crates/wasi/wit/deps/clocks/timezone.wit
+++ /dev/null
@@ -1,48 +0,0 @@
-interface timezone {
-    use wall-clock.{datetime};
-
-    /// Return information needed to display the given `datetime`. This includes
-    /// the UTC offset, the time zone name, and a flag indicating whether
-    /// daylight saving time is active.
-    ///
-    /// If the timezone cannot be determined for the given `datetime`, return a
-    /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight
-    /// saving time.
-    display: func(when: datetime) -> timezone-display;
-
-    /// The same as `display`, but only return the UTC offset.
-    utc-offset: func(when: datetime) -> s32;
-
-    /// Information useful for displaying the timezone of a specific `datetime`.
-    ///
-    /// This information may vary within a single `timezone` to reflect daylight
-    /// saving time adjustments.
-    record timezone-display {
-        /// The number of seconds difference between UTC time and the local
-        /// time of the timezone.
-        ///
-        /// The returned value will always be less than 86400 which is the
-        /// number of seconds in a day (24*60*60).
-        ///
-        /// In implementations that do not expose an actual time zone, this
-        /// should return 0.
-        utc-offset: s32,
-
-        /// The abbreviated name of the timezone to display to a user. The name
-        /// `UTC` indicates Coordinated Universal Time. Otherwise, this should
-        /// reference local standards for the name of the time zone.
-        ///
-        /// In implementations that do not expose an actual time zone, this
-        /// should be the string `UTC`.
-        ///
-        /// In time zones that do not have an applicable name, a formatted
-        /// representation of the UTC offset may be returned, such as `-04:00`.
-        name: string,
-
-        /// Whether daylight saving time is active.
-        ///
-        /// In implementations that do not expose an actual time zone, this
-        /// should return false.
-        in-daylight-saving-time: bool,
-    }
-}
diff --git a/crates/wasi/wit/deps/clocks/world.wit b/crates/wasi/wit/deps/clocks/world.wit
index a53d025b8..046145582 100644
--- a/crates/wasi/wit/deps/clocks/world.wit
+++ b/crates/wasi/wit/deps/clocks/world.wit
@@ -3,5 +3,4 @@ package wasi:clocks@0.2.0-rc-2023-11-05;
 world imports {
     import monotonic-clock;
     import wall-clock;
-    import timezone;
 }
diff --git a/crates/wasi/wit/deps/http/handler.wit b/crates/wasi/wit/deps/http/handler.wit
index 21b97a354..a34a0649d 100644
--- a/crates/wasi/wit/deps/http/handler.wit
+++ b/crates/wasi/wit/deps/http/handler.wit
@@ -22,7 +22,9 @@ interface incoming-handler {
 /// This interface defines a handler of outgoing HTTP Requests. It should be
 /// imported by components which wish to make HTTP Requests.
 interface outgoing-handler {
-  use types.{outgoing-request, request-options, future-incoming-response, error};
+  use types.{
+    outgoing-request, request-options, future-incoming-response, error-code
+  };
 
   /// This function is invoked with an outgoing HTTP Request, and it returns
   /// a resource `future-incoming-response` which represents an HTTP Response
@@ -37,5 +39,5 @@ interface outgoing-handler {
   handle: func(
     request: outgoing-request,
     options: option<request-options>
-  ) -> result<future-incoming-response, error>;
+  ) -> result<future-incoming-response, error-code>;
 }
diff --git a/crates/wasi/wit/deps/http/proxy.wit b/crates/wasi/wit/deps/http/proxy.wit
index 3fa8b9c63..4bc4eb644 100644
--- a/crates/wasi/wit/deps/http/proxy.wit
+++ b/crates/wasi/wit/deps/http/proxy.wit
@@ -8,7 +8,6 @@ world proxy {
   /// HTTP proxies have access to time and randomness.
   import wasi:clocks/wall-clock@0.2.0-rc-2023-11-05;
   import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/timezone@0.2.0-rc-2023-11-05;
   import wasi:random/random@0.2.0-rc-2023-11-05;
 
   /// Proxies have standard output and error streams which are expected to
diff --git a/crates/wasi/wit/deps/http/types.wit b/crates/wasi/wit/deps/http/types.wit
index fac47f61e..41226894e 100644
--- a/crates/wasi/wit/deps/http/types.wit
+++ b/crates/wasi/wit/deps/http/types.wit
@@ -3,7 +3,7 @@
 /// their headers, trailers, and bodies.
 interface types {
   use wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05.{duration};
-  use wasi:io/streams@0.2.0-rc-2023-11-05.{input-stream, output-stream};
+  use wasi:io/streams@0.2.0-rc-2023-11-05.{input-stream, output-stream, error as stream-error};
   use wasi:io/poll@0.2.0-rc-2023-11-05.{pollable};
 
   /// This type corresponds to HTTP standard Methods.
@@ -27,21 +27,100 @@ interface types {
     other(string)
   }
 
-  /// TODO: perhaps better align with HTTP semantics?
-  /// This type enumerates the different kinds of errors that may occur when
-  /// initially returning a response.
-  variant error {
-    invalid-url(string),
-    timeout-error(string),
-    protocol-error(string),
-    unexpected-error(string)
+  /// These cases are inspired by the IANA HTTP Proxy Error Types:
+  ///   https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-http-proxy-error-types
+  variant error-code {
+    DNS-timeout,
+    DNS-error(DNS-error-payload),
+    destination-not-found,
+    destination-unavailable,
+    destination-IP-prohibited,
+    destination-IP-unroutable,
+    connection-refused,
+    connection-terminated,
+    connection-timeout,
+    connection-read-timeout,
+    connection-write-timeout,
+    connection-limit-reached,
+    TLS-protocol-error,
+    TLS-certificate-error,
+    TLS-alert-received(TLS-alert-received-payload),
+    HTTP-request-denied,
+    HTTP-request-length-required,
+    HTTP-request-body-size(option<u64>),
+    HTTP-request-method-invalid,
+    HTTP-request-URI-invalid,
+    HTTP-request-URI-too-long,
+    HTTP-request-header-section-size(option<u32>),
+    HTTP-request-header-size(option<field-size-payload>),
+    HTTP-request-trailer-section-size(option<u32>),
+    HTTP-request-trailer-size(field-size-payload),
+    HTTP-response-incomplete,
+    HTTP-response-header-section-size(option<u32>),
+    HTTP-response-header-size(field-size-payload),
+    HTTP-response-body-size(option<u64>),
+    HTTP-response-trailer-section-size(option<u32>),
+    HTTP-response-trailer-size(field-size-payload),
+    HTTP-response-transfer-coding(option<string>),
+    HTTP-response-content-coding(option<string>),
+    HTTP-response-timeout,
+    HTTP-upgrade-failed,
+    HTTP-protocol-error,
+    loop-detected,
+    configuration-error,
+    /// This is a catch-all error for anything that doesn't fit cleanly into a
+    /// more specific case. It also includes an optional string for an
+    /// unstructured description of the error. Users should not depend on the
+    /// string for diagnosing errors, as it's not required to be consistent
+    /// between implementations.
+    internal-error(option<string>)
+  }
+
+  /// Defines the case payload type for `DNS-error` above:
+  record DNS-error-payload {
+    rcode: option<string>,
+    info-code: option<u16>
+  }
+
+  /// Defines the case payload type for `TLS-alert-received` above:
+  record TLS-alert-received-payload {
+    alert-id: option<u8>,
+    alert-message: option<string>
+  }
+
+  /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above:
+  record field-size-payload {
+    field-name: option<string>,
+    field-size: option<u32>
   }
 
-  /// This tyep enumerates the different kinds of errors that may occur when
+  /// Attempts to extract a http-related `error` from the stream `error`
+  /// provided.
+  ///
+  /// Stream operations which return `stream-error::last-operation-failed` have
+  /// a payload with more information about the operation that failed. This
+  /// payload can be passed through to this function to see if there's
+  /// http-related information about the error to return.
+  ///
+  /// Note that this function is fallible because not all stream-related errors
+  /// are http-related errors.
+  http-error-code: func(err: borrow<stream-error>) -> option<error-code>;
+
+  /// This type enumerates the different kinds of errors that may occur when
   /// setting or appending to a `fields` resource.
   variant header-error {
+    /// This error indicates that a `field-key` or `field-value` was
+    /// syntactically invalid when used with an operation that sets headers in a
+    /// `fields`.
     invalid-syntax,
+
+    /// This error indicates that a forbidden `field-key` was used when trying
+    /// to set a header in a `fields`.
     forbidden,
+
+    /// This error indicates that the operation on the `fields` was not
+    /// permitted because the fields are immutable.
+    immutable,
   }
 
   /// Field keys are always strings.
@@ -82,21 +161,14 @@ interface types {
 
     /// Set all of the values for a key. Clears any existing values for that
     /// key, if they have been set.
-    ///
-    /// The operation can fail if the name or value arguments are invalid, or if
-    /// the name is forbidden.
     set: func(name: field-key, value: list<field-value>) -> result<_, header-error>;
 
     /// Delete all values for a key. Does nothing if no values for the key
-    /// exist. Returns and error if the `field-key` is syntactically invalid, or
-    /// if the `field-key` is forbidden.
+    /// exist.
     delete: func(name: field-key) -> result<_, header-error>;
 
     /// Append a value for a key. Does not change or delete any existing
     /// values for that key.
-    ///
-    /// The operation can fail if the name or value arguments are invalid, or if
-    /// the name is forbidden.
     append: func(name: field-key, value: field-value) -> result<_, header-error>;
 
     /// Retrieve the full set of keys and values in the Fields. Like the
@@ -149,7 +221,7 @@ interface types {
   resource outgoing-request {
 
     /// Construct a new `outgoing-request` with a default `method` of `GET`, and
-    /// default values for `path-with-query`, `scheme`, and `authority.
+    /// `none` values for `path-with-query`, `scheme`, and `authority`.
     ///
     /// * `headers` is the HTTP Headers for the Request.
     ///
@@ -262,7 +334,7 @@ interface types {
     /// implementation determine how to respond with an HTTP error response.
     set: static func(
       param: response-outparam,
-      response: result<outgoing-response, error>,
+      response: result<outgoing-response, error-code>,
     );
   }
 
@@ -337,7 +409,7 @@ interface types {
     /// as well as any trailers, were received successfully, or that an error
     /// occured receiving them. The optional `trailers` indicates whether or not
     /// trailers were present in the body.
-    get: func() -> option<result<option<trailers>, error>>;
+    get: func() -> option<result<option<trailers>, error-code>>;
   }
 
   /// Represents an outgoing HTTP Response.
@@ -433,7 +505,7 @@ interface types {
     /// occured. Errors may also occur while consuming the response body,
     /// but those will be reported by the `incoming-body` and its
     /// `output-stream` child.
-    get: func() -> option<result<result<incoming-response, error>>>;
+    get: func() -> option<result<result<incoming-response, error-code>>>;
 
   }
 }
diff --git a/crates/wasi/wit/deps/io/error.wit b/crates/wasi/wit/deps/io/error.wit
new file mode 100644
index 000000000..b45c594e6
--- /dev/null
+++ b/crates/wasi/wit/deps/io/error.wit
@@ -0,0 +1,34 @@
+package wasi:io@0.2.0-rc-2023-11-05;
+
+
+interface error {
+    /// A resource which represents some error information.
+    ///
+    /// The only method provided by this resource is `to-debug-string`,
+    /// which provides some human-readable information about the error.
+    ///
+    /// In the `wasi:io` package, this resource is returned through the
+    /// `wasi:io/streams/stream-error` type.
+    ///
+    /// To provide more specific error information, other interfaces may
+    /// provide functions to further "downcast" this error into more specific
+    /// error information. For example, `error`s returned in streams derived
+    /// from filesystem types to be described using the filesystem's own
+    /// error-code type, using the function
+    /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter
+    /// `borrow<error>` and returns
+    /// `option<wasi:filesystem/types/error-code>`.
+    ///
+    /// The set of functions which can "downcast" an `error` into a more
+    /// concrete type is open.
+    resource error {
+        /// Returns a string that is suitable to assist humans in debugging
+        /// this error.
+        ///
+        /// WARNING: The returned string should not be consumed mechanically!
+        /// It may change across platforms, hosts, or other implementation
+        /// details. Parsing this string is a major platform-compatibility
+        /// hazard.
+        to-debug-string: func() -> string;
+    }
+}
diff --git a/crates/wasi/wit/deps/io/streams.wit b/crates/wasi/wit/deps/io/streams.wit
index 9442f60c0..45062505b 100644
--- a/crates/wasi/wit/deps/io/streams.wit
+++ b/crates/wasi/wit/deps/io/streams.wit
@@ -6,6 +6,7 @@ package wasi:io@0.2.0-rc-2023-11-05;
 /// In the future, the component model is expected to add built-in stream types;
 /// when it does, they are expected to subsume this API.
 interface streams {
+    use error.{error};
     use poll.{pollable};
 
     /// An error for input-stream and output-stream operations.
@@ -20,26 +21,6 @@ interface streams {
         closed
     }
 
-    /// Contextual error information about the last failure that happened on
-    /// a read, write, or flush from an `input-stream` or `output-stream`.
-    ///
-    /// This type is returned through the `stream-error` type whenever an
-    /// operation on a stream directly fails or an error is discovered
-    /// after-the-fact, for example when a write's failure shows up through a
-    /// later `flush` or `check-write`.
-    ///
-    /// Interfaces such as `wasi:filesystem/types` provide functionality to
-    /// further "downcast" this error into interface-specific error information.
-    resource error {
-        /// Returns a string that's suitable to assist humans in debugging this
-        /// error.
-        ///
-        /// The returned string will change across platforms and hosts which
-        /// means that parsing it, for example, would be a
-        /// platform-compatibility hazard.
-        to-debug-string: func() -> string;
-    }
-
     /// An input bytestream.
     ///
     /// `input-stream`s are *non-blocking* to the extent practical on underlying
@@ -51,21 +32,20 @@ interface streams {
     resource input-stream {
         /// Perform a non-blocking read from the stream.
         ///
-        /// This function returns a list of bytes containing the data that was
-        /// read, along with a `stream-status` which, indicates whether further
-        /// reads are expected to produce data. The returned list will contain up to
-        /// `len` bytes; it may return fewer than requested, but not more. An
-        /// empty list and `stream-status:open` indicates no more data is
-        /// available at this time, and that the pollable given by `subscribe`
-        /// will be ready when more data is available.
+        /// This function returns a list of bytes containing the read data,
+        /// when successful. The returned list will contain up to `len` bytes;
+        /// it may return fewer than requested, but not more. The list is
+        /// empty when no bytes are available for reading at this time. The
+        /// pollable given by `subscribe` will be ready when more bytes are
+        /// available.
         ///
-        /// Once a stream has reached the end, subsequent calls to `read` or
-        /// `skip` will always report `stream-status:ended` rather than producing more
-        /// data.
+        /// This function fails with a `stream-error` when the operation
+        /// encounters an error, giving `last-operation-failed`, or when the
+        /// stream is closed, giving `closed`.
         ///
-        /// When the caller gives a `len` of 0, it represents a request to read 0
-        /// bytes. This read should  always succeed and return an empty list and
-        /// the current `stream-status`.
+        /// When the caller gives a `len` of 0, it represents a request to
+        /// read 0 bytes. If the stream is still open, this call should
+        /// succeed and return an empty list, or otherwise fail with `closed`.
         ///
         /// The `len` parameter is a `u64`, which could represent a list of u8 which
         /// is not possible to allocate in wasm32, or not desirable to allocate as
@@ -77,24 +57,16 @@ interface streams {
         ) -> result<list<u8>, stream-error>;
 
         /// Read bytes from a stream, after blocking until at least one byte can
-        /// be read. Except for blocking, identical to `read`.
+        /// be read. Except for blocking, behavior is identical to `read`.
         blocking-read: func(
             /// The maximum number of bytes to read
             len: u64
         ) -> result<list<u8>, stream-error>;
 
-        /// Skip bytes from a stream.
-        ///
-        /// This is similar to the `read` function, but avoids copying the
-        /// bytes into the instance.
-        ///
-        /// Once a stream has reached the end, subsequent calls to read or
-        /// `skip` will always report end-of-stream rather than producing more
-        /// data.
+        /// Skip bytes from a stream. Returns number of bytes skipped.
         ///
-        /// This function returns the number of bytes skipped, along with a
-        /// `stream-status` indicating whether the end of the stream was
-        /// reached. The returned value will be at most `len`; it may be less.
+        /// Behaves identical to `read`, except instead of returning a list
+        /// of bytes, returns the number of bytes consumed from the stream.
         skip: func(
             /// The maximum number of bytes to skip.
             len: u64,
diff --git a/crates/wasi/wit/main.wit b/crates/wasi/wit/main.wit
deleted file mode 100644
index 90aa28ad3..000000000
--- a/crates/wasi/wit/main.wit
+++ /dev/null
@@ -1,33 +0,0 @@
-package wasmtime:wasi;
-
-// All of the same imports available in the wasi:cli/command world, but no
-// export required:
-world preview1-adapter-reactor {
-  import wasi:clocks/wall-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-05;
-  import wasi:clocks/timezone@0.2.0-rc-2023-11-05;
-  import wasi:filesystem/types@0.2.0-rc-2023-11-05;
-  import wasi:filesystem/preopens@0.2.0-rc-2023-11-05;
-  import wasi:sockets/instance-network@0.2.0-rc-2023-11-05;
-  import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-11-05;
-  import wasi:sockets/network@0.2.0-rc-2023-11-05;
-  import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-11-05;
-  import wasi:sockets/tcp@0.2.0-rc-2023-11-05;
-  import wasi:sockets/udp-create-socket@0.2.0-rc-2023-11-05;
-  import wasi:sockets/udp@0.2.0-rc-2023-11-05;
-  import wasi:random/random@0.2.0-rc-2023-11-05;
-  import wasi:random/insecure@0.2.0-rc-2023-11-05;
-  import wasi:random/insecure-seed@0.2.0-rc-2023-11-05;
-  import wasi:io/poll@0.2.0-rc-2023-11-05;
-  import wasi:io/streams@0.2.0-rc-2023-11-05;
-  import wasi:cli/environment@0.2.0-rc-2023-11-05;
-  import wasi:cli/exit@0.2.0-rc-2023-11-05;
-  import wasi:cli/stdin@0.2.0-rc-2023-11-05;
-  import wasi:cli/stdout@0.2.0-rc-2023-11-05;
-  import wasi:cli/stderr@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-input@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-output@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stdin@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stdout@0.2.0-rc-2023-11-05;
-  import wasi:cli/terminal-stderr@0.2.0-rc-2023-11-05;
-}
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index 927cc755b..5aad87dec 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -14,7 +14,9 @@ use wasmtime::{Engine, Store, StoreLimits};
 use wasmtime_wasi::preview2::{
     self, StreamError, StreamResult, Table, WasiCtx, WasiCtxBuilder, WasiView,
 };
-use wasmtime_wasi_http::{body::HyperOutgoingBody, WasiHttpCtx, WasiHttpView};
+use wasmtime_wasi_http::{
+    bindings::http::types as http_types, body::HyperOutgoingBody, WasiHttpCtx, WasiHttpView,
+};
 
 #[cfg(feature = "wasi-nn")]
 use wasmtime_wasi_nn::WasiNnCtx;
@@ -363,9 +365,16 @@ impl hyper::service::Service<Request> for ProxyHandler {
 
             let mut store = inner.cmd.new_store(&inner.engine, req_id)?;
 
-            let req = store
-                .data_mut()
-                .new_incoming_request(req.map(|body| body.map_err(|e| e.into()).boxed()))?;
+            let req = store.data_mut().new_incoming_request(req.map(|body| {
+                body.map_err(|err| {
+                    if err.is_timeout() {
+                        http_types::ErrorCode::HttpResponseTimeout
+                    } else {
+                        http_types::ErrorCode::InternalError(Some(err.message().to_string()))
+                    }
+                })
+                .boxed()
+            }))?;
 
             let out = store.data_mut().new_response_outparam(sender)?;
 


Test patch:
diff --git a/crates/test-programs/src/bin/api_proxy_streaming.rs b/crates/test-programs/src/bin/api_proxy_streaming.rs
index 92ad6fb02..9c54eb474 100644
--- a/crates/test-programs/src/bin/api_proxy_streaming.rs
+++ b/crates/test-programs/src/bin/api_proxy_streaming.rs
@@ -451,7 +451,7 @@ mod executor {
 
     pub fn outgoing_request_send(
         request: OutgoingRequest,
-    ) -> impl Future<Output = Result<IncomingResponse, types::Error>> {
+    ) -> impl Future<Output = Result<IncomingResponse, types::ErrorCode>> {
         future::poll_fn({
             let response = outgoing_handler::handle(request, None);
 
diff --git a/crates/test-programs/src/bin/http_outbound_request_invalid_dnsname.rs b/crates/test-programs/src/bin/http_outbound_request_invalid_dnsname.rs
index 100f57d6d..0b6deddef 100644
--- a/crates/test-programs/src/bin/http_outbound_request_invalid_dnsname.rs
+++ b/crates/test-programs/src/bin/http_outbound_request_invalid_dnsname.rs
@@ -1,4 +1,4 @@
-use test_programs::wasi::http::types::{Method, Scheme};
+use test_programs::wasi::http::types::{ErrorCode, Method, Scheme};
 
 fn main() {
     let res = test_programs::http::request(
@@ -10,9 +10,13 @@ fn main() {
         None,
     );
 
-    let error = res.unwrap_err().to_string();
+    let e = res.unwrap_err();
     assert!(
-        error.starts_with("Error::InvalidUrl(\""),
-        "bad error: {error}"
+        matches!(
+            e.downcast_ref::<ErrorCode>()
+                .expect("expected a wasi-http ErrorCode"),
+            ErrorCode::DnsError(_) | ErrorCode::ConnectionRefused,
+        ),
+        "Unexpected error: {e:#?}"
     );
 }
diff --git a/crates/test-programs/src/bin/http_outbound_request_invalid_header.rs b/crates/test-programs/src/bin/http_outbound_request_invalid_header.rs
index 09f173298..65931dd61 100644
--- a/crates/test-programs/src/bin/http_outbound_request_invalid_header.rs
+++ b/crates/test-programs/src/bin/http_outbound_request_invalid_header.rs
@@ -1,4 +1,4 @@
-use test_programs::wasi::http::types::{HeaderError, Headers};
+use test_programs::wasi::http::types::{HeaderError, Headers, OutgoingRequest};
 
 fn main() {
     let hdrs = Headers::new();
@@ -57,4 +57,22 @@ fn main() {
         Headers::from_list(&[("ok-header-name".to_owned(), b"bad\nvalue".to_vec())]),
         Err(HeaderError::InvalidSyntax)
     ));
+
+    let req = OutgoingRequest::new(hdrs);
+    let hdrs = req.headers();
+
+    assert!(matches!(
+        hdrs.set(&"Content-Length".to_owned(), &[b"10".to_vec()]),
+        Err(HeaderError::Immutable),
+    ));
+
+    assert!(matches!(
+        hdrs.append(&"Content-Length".to_owned(), &b"10".to_vec()),
+        Err(HeaderError::Immutable),
+    ));
+
+    assert!(matches!(
+        hdrs.delete(&"Content-Length".to_owned()),
+        Err(HeaderError::Immutable),
+    ));
 }
diff --git a/crates/test-programs/src/bin/http_outbound_request_invalid_version.rs b/crates/test-programs/src/bin/http_outbound_request_invalid_version.rs
index 59c652fc2..568026859 100644
--- a/crates/test-programs/src/bin/http_outbound_request_invalid_version.rs
+++ b/crates/test-programs/src/bin/http_outbound_request_invalid_version.rs
@@ -1,17 +1,14 @@
-use test_programs::wasi::http::types::{Method, Scheme};
+use test_programs::wasi::http::types::{ErrorCode, Method, Scheme};
 
 fn main() {
     let addr = std::env::var("HTTP_SERVER").unwrap();
     let res =
         test_programs::http::request(Method::Connect, Scheme::Http, &addr, "/", None, Some(&[]));
 
-    let error = res.unwrap_err().to_string();
-    if !error.starts_with("Error::ProtocolError(\"") {
-        panic!(
-            r#"assertion failed: `(left == right)`
-      left: `"{error}"`,
-      right: `"Error::ProtocolError(\"invalid HTTP version parsed\")"`
-            or `"Error::ProtocolError(\"operation was canceled\")"`)"#
-        )
-    }
+    assert!(matches!(
+        res.unwrap_err()
+            .downcast::<ErrorCode>()
+            .expect("expected a wasi-http ErrorCode"),
+        ErrorCode::HttpProtocolError,
+    ));
 }
diff --git a/crates/test-programs/src/bin/http_outbound_request_unknown_method.rs b/crates/test-programs/src/bin/http_outbound_request_unknown_method.rs
index 6211bf0c8..50d5751f5 100644
--- a/crates/test-programs/src/bin/http_outbound_request_unknown_method.rs
+++ b/crates/test-programs/src/bin/http_outbound_request_unknown_method.rs
@@ -2,7 +2,7 @@ use test_programs::wasi::http::types::{Method, Scheme};
 
 fn main() {
     let res = test_programs::http::request(
-        Method::Other("OTHER".to_owned()),
+        Method::Other("bad\nmethod".to_owned()),
         Scheme::Http,
         "localhost:3000",
         "/",
@@ -10,9 +10,6 @@ fn main() {
         None,
     );
 
-    let error = res.unwrap_err();
-    assert_eq!(
-        error.to_string(),
-        "Error::InvalidUrl(\"unknown method OTHER\")"
-    );
+    // This error arises from input validation in the `set_method` function on `OutgoingRequest`.
+    assert_eq!(res.unwrap_err().to_string(), "failed to set method");
 }
diff --git a/crates/test-programs/src/bin/http_outbound_request_unsupported_scheme.rs b/crates/test-programs/src/bin/http_outbound_request_unsupported_scheme.rs
index 262cb11e6..84ce1a5ba 100644
--- a/crates/test-programs/src/bin/http_outbound_request_unsupported_scheme.rs
+++ b/crates/test-programs/src/bin/http_outbound_request_unsupported_scheme.rs
@@ -1,4 +1,4 @@
-use test_programs::wasi::http::types::{Method, Scheme};
+use test_programs::wasi::http::types::{ErrorCode, Method, Scheme};
 
 fn main() {
     let res = test_programs::http::request(
@@ -10,9 +10,10 @@ fn main() {
         None,
     );
 
-    let error = res.unwrap_err();
-    assert_eq!(
-        error.to_string(),
-        "Error::InvalidUrl(\"unsupported scheme WS\")"
-    );
+    assert!(matches!(
+        res.unwrap_err()
+            .downcast::<ErrorCode>()
+            .expect("expected a wasi-http ErrorCode"),
+        ErrorCode::HttpProtocolError,
+    ));
 }
diff --git a/crates/test-programs/src/http.rs b/crates/test-programs/src/http.rs
index 3dbcb6846..64a182ac8 100644
--- a/crates/test-programs/src/http.rs
+++ b/crates/test-programs/src/http.rs
@@ -113,19 +113,16 @@ pub fn request(
     http_types::OutgoingBody::finish(outgoing_body, None);
 
     let incoming_response = match future_response.get() {
-        Some(result) => result.map_err(|_| anyhow!("incoming response errored"))?,
+        Some(result) => result.map_err(|()| anyhow!("response already taken"))?,
         None => {
             let pollable = future_response.subscribe();
             pollable.block();
             future_response
                 .get()
                 .expect("incoming response available")
-                .map_err(|_| anyhow!("incoming response errored"))?
+                .map_err(|()| anyhow!("response already taken"))?
         }
-    }
-    // TODO: maybe anything that appears in the Result<_, E> position should impl
-    // Error? anyway, just use its Debug here:
-    .map_err(|e| anyhow!("{e:?}"))?;
+    }?;
 
     drop(future_response);
 
diff --git a/crates/wasi-http/tests/all/main.rs b/crates/wasi-http/tests/all/main.rs
index 45094d52e..323d41892 100644
--- a/crates/wasi-http/tests/all/main.rs
+++ b/crates/wasi-http/tests/all/main.rs
@@ -15,7 +15,7 @@ use wasmtime_wasi::preview2::{
     self, pipe::MemoryOutputPipe, Table, WasiCtx, WasiCtxBuilder, WasiView,
 };
 use wasmtime_wasi_http::{
-    bindings::http::types::Error,
+    bindings::http::types::ErrorCode,
     body::HyperIncomingBody,
     types::{self, HostFutureIncomingResponse, IncomingResponseInternal, OutgoingRequest},
     WasiHttpCtx, WasiHttpView,
@@ -128,7 +128,7 @@ async fn run_wasi_http(
     component_filename: &str,
     req: hyper::Request<HyperIncomingBody>,
     send_request: Option<RequestSender>,
-) -> anyhow::Result<Result<hyper::Response<Collected<Bytes>>, Error>> {
+) -> anyhow::Result<Result<hyper::Response<Collected<Bytes>>, ErrorCode>> {
     let stdout = MemoryOutputPipe::new(4096);
     let stderr = MemoryOutputPipe::new(4096);
     let table = Table::new();
@@ -457,7 +457,7 @@ async fn do_wasi_http_echo(uri: &str, url_header: Option<&str>) -> Result<()> {
 
     let request = request.body(BoxBody::new(StreamBody::new(stream::iter(
         body.chunks(16 * 1024)
-            .map(|chunk| Ok::<_, Error>(Frame::data(Bytes::copy_from_slice(chunk))))
+            .map(|chunk| Ok::<_, ErrorCode>(Frame::data(Bytes::copy_from_slice(chunk))))
             .collect::<Vec<_>>(),
     ))))?;
 
diff --git a/crates/wasi-http/wit/test.wit b/crates/wasi-http/wit/test.wit
index 5e7557ed6..fc31f6ef3 100644
--- a/crates/wasi-http/wit/test.wit
+++ b/crates/wasi-http/wit/test.wit
@@ -1,3 +1,5 @@
+package wasmtime:wasi;
+
 // only used as part of `test-programs`
 world test-reactor {
   include wasi:cli/reactor@0.2.0-rc-2023-11-05;
diff --git a/crates/wasi/wit/test.wit b/crates/wasi/wit/test.wit
index 5e7557ed6..fc31f6ef3 100644
--- a/crates/wasi/wit/test.wit
+++ b/crates/wasi/wit/test.wit
@@ -1,3 +1,5 @@
+package wasmtime:wasi;
+
 // only used as part of `test-programs`
 world test-reactor {
   include wasi:cli/reactor@0.2.0-rc-2023-11-05;