├── poem ├── .gitignore ├── src │ ├── route │ │ ├── internal │ │ │ └── mod.rs │ │ └── mod.rs │ ├── listener │ │ ├── certs │ │ │ ├── identity.p12 │ │ │ ├── openssl.cnf │ │ │ └── cert1.pem │ │ ├── tls.rs │ │ └── acme │ │ │ ├── mod.rs │ │ │ └── keypair.rs │ ├── endpoint │ │ ├── to_response.rs │ │ ├── map_to_response.rs │ │ ├── before.rs │ │ ├── after.rs │ │ ├── map.rs │ │ ├── and_then.rs │ │ ├── around.rs │ │ ├── inspect_all_err.rs │ │ ├── catch_all_error.rs │ │ ├── inspect_err.rs │ │ ├── catch_error.rs │ │ └── mod.rs │ ├── session │ │ ├── mod.rs │ │ └── session_storage.rs │ └── web │ │ └── addr.rs └── LICENSE-MIT ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── question.md │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml └── workflows │ └── code-coverage.yml ├── examples ├── poem │ ├── static-files │ │ ├── files │ │ │ ├── #a.txt │ │ │ ├── 你好.txt │ │ │ ├── 文件 │ │ │ │ ├── a │ │ │ │ │ └── a.txt │ │ │ │ ├── 你好.txt │ │ │ │ └── poem-logo.jpg │ │ │ ├── images │ │ │ │ └── poem-logo.jpg │ │ │ ├── subpage │ │ │ │ ├── res │ │ │ │ │ └── poem-logo.jpg │ │ │ │ └── index.html │ │ │ └── index.html │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── tera-templating │ │ ├── templates │ │ │ └── index.html.tera │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── worker-hello-world │ │ ├── .gitignore │ │ ├── wrangler.toml │ │ ├── README.md │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── i18n │ │ ├── resources │ │ │ ├── zh-CN │ │ │ │ └── simple.ftl │ │ │ ├── en-US │ │ │ │ └── simple.ftl │ │ │ └── fr │ │ │ │ └── simple.ftl │ │ └── Cargo.toml │ ├── opentelemetry-jaeger │ │ ├── jaeger.png │ │ ├── README.md │ │ └── Cargo.toml │ ├── embed-files │ │ ├── files │ │ │ ├── images │ │ │ │ └── poem-logo.jpg │ │ │ └── index.html │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── add-data │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── basic-auth │ │ └── Cargo.toml │ ├── catch-panic │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── handling-404 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── hello-world │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── middleware │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── unix-socket │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── middleware_fn │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── nested-routing │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── tls │ │ └── Cargo.toml │ ├── combined-listeners │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── upload │ │ └── Cargo.toml │ ├── custom-error │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── acme-alpn-01 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── acme-http-01 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── requestid │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── cookie-session │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── csrf │ │ └── Cargo.toml │ ├── custom-extractor │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── auth │ │ └── Cargo.toml │ ├── json │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── local-server-with-browser │ │ └── Cargo.toml │ ├── tokio-metrics │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── async-graphql │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── tls-reload │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── main.rs │ │ │ └── cert.pem │ ├── acme-expanded-http-01 │ │ └── Cargo.toml │ ├── websocket-chat │ │ └── Cargo.toml │ ├── lambda-hello-world │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── sse │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── mongodb │ │ ├── Cargo.toml │ │ └── README.md │ ├── graceful-shutdown │ │ └── Cargo.toml │ ├── tower-layer │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── redis-session │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── openapi │ ├── custom-payload │ │ ├── data.bcs │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── main.rs │ ├── todos │ │ ├── .gitignore │ │ ├── migrations │ │ │ └── 20211218110925_todos.sql │ │ ├── Cargo.toml │ │ └── README.md │ ├── log-with-operation-id │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── union │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── generics │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── auth-basic │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── hello-world │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── combined-apis │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── poem-extractor │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── poem-middleware │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── uniform-response │ │ └── Cargo.toml │ ├── upload │ │ └── Cargo.toml │ ├── sse │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ ├── content-type-accept │ │ ├── Cargo.toml │ │ └── README.md │ ├── users-crud │ │ └── Cargo.toml │ ├── auth-apikey │ │ └── Cargo.toml │ ├── auth-multiple │ │ └── Cargo.toml │ └── auth-github │ │ └── Cargo.toml ├── .gitignore ├── disabled │ └── tonic │ │ ├── build.rs │ │ ├── proto │ │ └── helloworld.proto │ │ ├── Cargo.toml │ │ └── src │ │ ├── client.rs │ │ └── main.rs ├── grpc │ ├── helloworld │ │ ├── build.rs │ │ ├── Cargo.toml │ │ ├── src │ │ │ ├── client.rs │ │ │ └── main.rs │ │ └── proto │ │ │ └── helloworld.proto │ ├── routeguide │ │ ├── build.rs │ │ ├── Cargo.toml │ │ └── src │ │ │ └── data.rs │ ├── helloworld_compressed │ │ ├── build.rs │ │ ├── Cargo.toml │ │ ├── src │ │ │ ├── client.rs │ │ │ └── main.rs │ │ └── proto │ │ │ └── helloworld.proto │ ├── helloworld_typename │ │ ├── build.rs │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── main.rs │ │ └── proto │ │ │ └── helloworld.proto │ ├── reflection │ │ ├── build.rs │ │ ├── Cargo.toml │ │ ├── proto │ │ │ └── helloworld.proto │ │ └── src │ │ │ └── main.rs │ ├── jsoncodec │ │ ├── build.rs │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── main.rs │ │ └── proto │ │ │ └── helloworld.proto │ └── middleware │ │ ├── build.rs │ │ ├── Cargo.toml │ │ ├── src │ │ ├── client.rs │ │ ├── main.rs │ │ └── middleware.rs │ │ └── proto │ │ └── helloworld.proto ├── mcpserver │ ├── counter │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── counter-streamable-http │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs └── Cargo.toml ├── logo.png ├── favicon.ico ├── .gitignore ├── poem-openapi ├── assets │ └── swagger-ui.jpg ├── src │ ├── types │ │ ├── external │ │ │ ├── prost_wkt_types │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── sqlx.rs │ │ └── multipart │ │ │ └── mod.rs │ ├── response │ │ └── mod.rs │ ├── ui │ │ ├── rapidoc │ │ │ └── oauth-receiver.html │ │ ├── mod.rs │ │ ├── stoplight_elements │ │ │ ├── mod.rs │ │ │ └── stoplight-elements.html │ │ ├── scalar │ │ │ └── mod.rs │ │ ├── openapi_explorer │ │ │ └── mod.rs │ │ └── redoc │ │ │ └── mod.rs │ ├── param │ │ └── mod.rs │ ├── validation │ │ ├── max_length.rs │ │ ├── multiple_of.rs │ │ ├── min_length.rs │ │ ├── max_items.rs │ │ ├── min_items.rs │ │ ├── pattern.rs │ │ ├── unique_items.rs │ │ ├── mod.rs │ │ ├── maximum.rs │ │ ├── minimum.rs │ │ ├── max_properties.rs │ │ └── min_properties.rs │ ├── docs │ │ ├── request.md │ │ ├── response_content.md │ │ ├── newtype.md │ │ ├── oauth_scopes.md │ │ └── tags.md │ ├── auth │ │ ├── bearer.rs │ │ ├── basic.rs │ │ └── api_key.rs │ └── path_util.rs └── LICENSE-MIT ├── poem-grpc ├── src │ ├── example_generated │ │ └── mod.rs │ ├── macros.rs │ ├── streaming.rs │ ├── route.rs │ ├── service.rs │ └── lib.rs ├── proto │ ├── health.proto │ └── test_harness.proto ├── build.rs ├── LICENSE-MIT └── CHANGELOG.md ├── poem-worker ├── src │ ├── lib.rs │ ├── context.rs │ └── body.rs └── Cargo.toml ├── .tarpaulin.toml ├── .rustfmt.toml ├── poem-mcpserver ├── src │ ├── protocol │ │ ├── mod.rs │ │ ├── resources.rs │ │ └── content.rs │ └── lib.rs ├── CHANGELOG.md └── Cargo.toml ├── poem-mcpserver-macros ├── Cargo.toml └── src │ └── lib.rs ├── poem-derive ├── src │ └── utils.rs ├── Cargo.toml └── LICENSE-MIT ├── poem-grpc-build ├── src │ ├── utils.rs │ ├── service_generator.rs │ └── lib.rs ├── Cargo.toml └── LICENSE-MIT ├── poem-openapi-derive ├── src │ ├── error.rs │ └── parameter_style.rs ├── Cargo.toml └── LICENSE-MIT ├── poem-lambda ├── Cargo.toml ├── LICENSE-MIT └── CHANGELOG.md ├── SECURITY.md └── LICENSE-MIT /poem/.gitignore: -------------------------------------------------------------------------------- 1 | test-socket 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: sunli829 2 | -------------------------------------------------------------------------------- /examples/poem/static-files/files/#a.txt: -------------------------------------------------------------------------------- 1 | #a -------------------------------------------------------------------------------- /examples/poem/static-files/files/你好.txt: -------------------------------------------------------------------------------- 1 | 你好! -------------------------------------------------------------------------------- /examples/poem/static-files/files/文件/a/a.txt: -------------------------------------------------------------------------------- 1 | abc -------------------------------------------------------------------------------- /examples/poem/static-files/files/文件/你好.txt: -------------------------------------------------------------------------------- 1 | 你好! -------------------------------------------------------------------------------- /examples/openapi/custom-payload/data.bcs: -------------------------------------------------------------------------------- 1 | JohnDoe -------------------------------------------------------------------------------- /examples/openapi/todos/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | todos.db* 3 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poem-web/poem/HEAD/logo.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poem-web/poem/HEAD/favicon.ico -------------------------------------------------------------------------------- /examples/poem/tera-templating/templates/index.html.tera: -------------------------------------------------------------------------------- 1 |

Hello {{ name }}

-------------------------------------------------------------------------------- /poem/src/route/internal/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod radix_tree; 2 | pub(crate) mod trie; 3 | -------------------------------------------------------------------------------- /examples/poem/worker-hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules 3 | .wrangler 4 | build -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | .DS_Store 5 | docs/gh-pages 6 | docs/book -------------------------------------------------------------------------------- /examples/poem/i18n/resources/zh-CN/simple.ftl: -------------------------------------------------------------------------------- 1 | hello-world = 你好世界! 2 | welcome = 欢迎 { $name }! 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | .vscode 5 | .DS_Store 6 | docs/gh-pages 7 | docs/book -------------------------------------------------------------------------------- /examples/poem/i18n/resources/en-US/simple.ftl: -------------------------------------------------------------------------------- 1 | hello-world = Hello world! 2 | welcome = welcome { $name }! 3 | -------------------------------------------------------------------------------- /examples/poem/i18n/resources/fr/simple.ftl: -------------------------------------------------------------------------------- 1 | hello-world = Bonjour le monde! 2 | welcome = Bienvenue { $name }! 3 | -------------------------------------------------------------------------------- /poem-openapi/assets/swagger-ui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poem-web/poem/HEAD/poem-openapi/assets/swagger-ui.jpg -------------------------------------------------------------------------------- /poem/src/listener/certs/identity.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poem-web/poem/HEAD/poem/src/listener/certs/identity.p12 -------------------------------------------------------------------------------- /poem-openapi/src/types/external/prost_wkt_types/mod.rs: -------------------------------------------------------------------------------- 1 | mod duration; 2 | mod r#struct; 3 | mod timestamp; 4 | mod value; 5 | -------------------------------------------------------------------------------- /poem-openapi/src/response/mod.rs: -------------------------------------------------------------------------------- 1 | //! Commonly used response types. 2 | 3 | #[cfg(feature = "static-files")] 4 | mod static_file; 5 | -------------------------------------------------------------------------------- /examples/poem/opentelemetry-jaeger/jaeger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poem-web/poem/HEAD/examples/poem/opentelemetry-jaeger/jaeger.png -------------------------------------------------------------------------------- /examples/poem/static-files/files/文件/poem-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poem-web/poem/HEAD/examples/poem/static-files/files/文件/poem-logo.jpg -------------------------------------------------------------------------------- /examples/poem/embed-files/files/images/poem-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poem-web/poem/HEAD/examples/poem/embed-files/files/images/poem-logo.jpg -------------------------------------------------------------------------------- /examples/poem/static-files/files/images/poem-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poem-web/poem/HEAD/examples/poem/static-files/files/images/poem-logo.jpg -------------------------------------------------------------------------------- /examples/poem/static-files/files/subpage/res/poem-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/poem-web/poem/HEAD/examples/poem/static-files/files/subpage/res/poem-logo.jpg -------------------------------------------------------------------------------- /poem-openapi/src/types/multipart/mod.rs: -------------------------------------------------------------------------------- 1 | //! Multipart related types. 2 | 3 | mod json; 4 | mod upload; 5 | 6 | pub use json::JsonField; 7 | pub use upload::Upload; 8 | -------------------------------------------------------------------------------- /examples/disabled/tonic/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), Box> { 2 | tonic_build::compile_protos("proto/helloworld.proto")?; 3 | Ok(()) 4 | } 5 | -------------------------------------------------------------------------------- /examples/poem/worker-hello-world/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "worker-hello-world" 2 | main = "build/worker/shim.mjs" 3 | compatibility_date = "2025-07-20" 4 | 5 | [build] 6 | command = "worker-build --release" 7 | -------------------------------------------------------------------------------- /examples/grpc/helloworld/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use poem_grpc_build::compile_protos; 4 | 5 | fn main() -> Result<()> { 6 | compile_protos(&["./proto/helloworld.proto"], &["./proto"]) 7 | } 8 | -------------------------------------------------------------------------------- /examples/grpc/routeguide/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use poem_grpc_build::Config; 4 | 5 | fn main() -> Result<()> { 6 | Config::new().compile(&["./proto/routeguide.proto"], &["./proto"]) 7 | } 8 | -------------------------------------------------------------------------------- /examples/grpc/helloworld_compressed/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use poem_grpc_build::compile_protos; 4 | 5 | fn main() -> Result<()> { 6 | compile_protos(&["./proto/helloworld.proto"], &["./proto"]) 7 | } 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Question' 3 | about: 'If something needs clarification' 4 | title: '' 5 | labels: question 6 | --- 7 | 8 | <!-- What is your question? Please be as specific as possible! --> -------------------------------------------------------------------------------- /poem-openapi/src/ui/rapidoc/oauth-receiver.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <head> 3 | <script charset="UTF-8">{:script}</script> 4 | </head> 5 | 6 | <body> 7 | <oauth-receiver> 8 | </oauth-receiver> 9 | </body> 10 | -------------------------------------------------------------------------------- /poem-grpc/src/example_generated/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module shows an example of code generated by the macro. **IT MUST NOT 2 | //! BE USED OUTSIDE THIS CRATE**. 3 | 4 | #![allow(missing_docs)] 5 | 6 | include!(concat!(env!("OUT_DIR"), "/routeguide.rs")); 7 | -------------------------------------------------------------------------------- /examples/openapi/todos/migrations/20211218110925_todos.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS todos 2 | ( 3 | id INTEGER PRIMARY KEY NOT NULL, 4 | description TEXT NOT NULL, 5 | done BOOLEAN NOT NULL DEFAULT 0 6 | ); -------------------------------------------------------------------------------- /poem-worker/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod body; 2 | pub(crate) mod req; 3 | 4 | mod cloudflare; 5 | pub use cloudflare::*; 6 | 7 | mod env; 8 | pub use env::*; 9 | 10 | mod context; 11 | pub use context::*; 12 | 13 | mod server; 14 | pub use server::*; 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Feature Request' 3 | about: 'Report a new feature to be implemented' 4 | title: '<Title>' 5 | labels: enhancement 6 | --- 7 | 8 | ## Description of the feature 9 | 10 | 11 | ## Code example (if possible) -------------------------------------------------------------------------------- /.tarpaulin.toml: -------------------------------------------------------------------------------- 1 | [all] 2 | workspace = true 3 | avoid_cfg_tarpaulin = true 4 | all-features = true 5 | exclude = [] 6 | exclude-files = [ 7 | "examples/**/*", 8 | "poem-derive/**/*", 9 | "poem-openapi-derive/**/*", 10 | "poem-grpc-build/**/*", 11 | ] 12 | -------------------------------------------------------------------------------- /examples/grpc/helloworld_typename/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use poem_grpc_build::Config; 4 | 5 | fn main() -> Result<()> { 6 | Config::new() 7 | .enable_type_names() 8 | .compile(&["./proto/helloworld.proto"], &["./proto"]) 9 | } 10 | -------------------------------------------------------------------------------- /examples/poem/worker-hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Poem with Cloudflare worker 2 | 3 | ## Prequirement 4 | 5 | - [worker-build](https://github.com/cloudflare/workers-rs/tree/main/worker-build) 6 | - [wranger](https://developers.cloudflare.com/workers/wrangler/install-and-update/) 7 | -------------------------------------------------------------------------------- /examples/grpc/reflection/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use poem_grpc_build::Config; 4 | 5 | fn main() -> Result<()> { 6 | Config::new() 7 | .file_descriptor_set_path("helloworld.bin") 8 | .compile(&["./proto/helloworld.proto"], &["./proto"]) 9 | } 10 | -------------------------------------------------------------------------------- /examples/disabled/tonic/proto/helloworld.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package helloworld; 4 | 5 | message HelloRequest { 6 | string name = 1; 7 | } 8 | 9 | message HelloReply { 10 | string message = 1; 11 | } 12 | 13 | service Greeter { 14 | rpc SayHello (HelloRequest) returns (HelloReply) {} 15 | } 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" -------------------------------------------------------------------------------- /examples/poem/add-data/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-add-data" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true -------------------------------------------------------------------------------- /examples/poem/static-files/files/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="UTF-8"> 5 | <title>Hello world 6 | 7 | 8 |
Poem Web Framework
9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/poem/tera-templating/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-tera-templating" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["full"] } 10 | tera = "1.17.1" 11 | once_cell = "1.17.0" 12 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | newline_style = "unix" 3 | # comments 4 | normalize_comments = true 5 | wrap_comments = true 6 | format_code_in_doc_comments = true 7 | # imports 8 | imports_granularity = "Crate" 9 | group_imports = "StdExternalCrate" 10 | # report 11 | #report_fixme="Unnumbered" 12 | #report_todo="Unnumbered" 13 | -------------------------------------------------------------------------------- /examples/poem/basic-auth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-basic-auth" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/catch-panic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-catch-unwind" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/handling-404/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-handling-404" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-hello-world" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/middleware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-middleware" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/unix-socket/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-unix-socket" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/mcpserver/counter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "counter" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | poem-mcpserver.workspace = true 8 | serde = { version = "1.0.219", features = ["derive"] } 9 | schemars = "1.0" 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] } 11 | -------------------------------------------------------------------------------- /examples/poem/embed-files/files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello world 6 | 7 | 8 |
Poem Web Framework
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/poem/middleware_fn/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-middleware-fn" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/nested-routing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-nested-routing" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/static-files/files/subpage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello world 6 | 7 | 8 |
Poem Web Framework
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/poem/tls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-tls" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["rustls"]} 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /poem-openapi/src/param/mod.rs: -------------------------------------------------------------------------------- 1 | //! Parameter types for the API operation. 2 | #[cfg(feature = "cookie")] 3 | mod cookie; 4 | mod header; 5 | mod path; 6 | mod query; 7 | 8 | #[cfg(feature = "cookie")] 9 | pub use cookie::{Cookie, CookiePrivate, CookieSigned}; 10 | pub use header::Header; 11 | pub use path::Path; 12 | pub use query::Query; 13 | -------------------------------------------------------------------------------- /examples/poem/combined-listeners/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-combined-listeners" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/i18n/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-i18n" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["i18n"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/upload/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-upload" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["multipart"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/grpc/jsoncodec/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use poem_grpc_build::Config; 4 | 5 | fn main() -> Result<()> { 6 | Config::new() 7 | .codec("::poem_grpc::codec::JsonCodec") 8 | .type_attribute(".", "#[derive(serde::Deserialize, serde::Serialize)]") 9 | .compile(&["./proto/helloworld.proto"], &["./proto"]) 10 | } 11 | -------------------------------------------------------------------------------- /examples/openapi/log-with-operation-id/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-log-with-operation-id" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | -------------------------------------------------------------------------------- /examples/poem/custom-error/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-custom-error" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | thiserror = "2.0" 12 | -------------------------------------------------------------------------------- /examples/grpc/middleware/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | use poem_grpc_build::Config; 4 | 5 | fn main() -> Result<()> { 6 | Config::new() 7 | .client_middleware("crate::middleware::ClientMiddleware") 8 | .server_middleware("crate::middleware::ServerMiddleware") 9 | .compile(&["./proto/helloworld.proto"], &["./proto"]) 10 | } 11 | -------------------------------------------------------------------------------- /examples/poem/acme-alpn-01/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-acme-alpn-01" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["acme"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/acme-http-01/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-acme-http-01" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["acme"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/requestid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-requestid" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["requestid"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Bug Report' 3 | about: 'Report a new bug' 4 | title: '' 5 | labels: bug 6 | --- 7 | 8 | ## Expected Behavior 9 | 10 | 11 | ## Actual Behavior 12 | 13 | 14 | ## Steps to Reproduce the Problem 15 | 16 | 1. 17 | 2. 18 | 3. 19 | 20 | ## Specifications 21 | 22 | - Version: 23 | - Platform: 24 | - Subsystem: -------------------------------------------------------------------------------- /examples/openapi/union/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-union" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/poem/cookie-session/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-cookie-session" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["session"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/poem/static-files/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-static-files" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["static-files"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/openapi/generics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-generics" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/poem/csrf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-csrf" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["csrf"] } 9 | serde.workspace = true 10 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/poem/custom-extractor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-custom-extractor" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | serde.workspace = true 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/openapi/auth-basic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-auth-basic" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/openapi/hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-hello-world" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/poem/auth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-auth" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["session"] } 9 | serde.workspace = true 10 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/poem/json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-json" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | serde.workspace = true 11 | serde_json.workspace = true 12 | tracing-subscriber.workspace = true 13 | -------------------------------------------------------------------------------- /examples/poem/local-server-with-browser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "local-server-with-browser" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | open.workspace = true 9 | poem.workspace = true 10 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/openapi/combined-apis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-combined-apis" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/openapi/poem-extractor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-poem-extractor" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/poem/embed-files/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-embed-files" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["embed"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | rust-embed = "8.0" 12 | -------------------------------------------------------------------------------- /examples/poem/tokio-metrics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-tokio-metrics" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["tokio-metrics"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time"] } 10 | tracing-subscriber.workspace = true 11 | -------------------------------------------------------------------------------- /examples/openapi/poem-middleware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-poem-middleware" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/poem/async-graphql/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-async-graphql" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | async-graphql = "7.0.7" 11 | slab = "0.4.4" 12 | tracing-subscriber.workspace = true 13 | -------------------------------------------------------------------------------- /examples/poem/tls-reload/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-tls-reload" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | async-stream = "0.3.2" 9 | poem = { workspace = true, features = ["rustls"]} 10 | tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/openapi/uniform-response/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-uniform-response" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/poem/acme-expanded-http-01/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-acme-expanded-http-01" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | poem = { path = "../../../poem", features = ["acme"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber = { version = "0.3.9", features = ["env-filter"] } 11 | -------------------------------------------------------------------------------- /examples/poem/websocket-chat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-websocket-chat" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["websocket"]} 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | futures-util.workspace = true 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/openapi/upload/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-upload" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["multipart", "tempfile"] } 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | tracing-subscriber.workspace = true -------------------------------------------------------------------------------- /examples/poem/lambda-hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-lambda-hello-world" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { path = "../../../poem", default-features = false } 9 | poem-lambda.workspace = true 10 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 11 | tracing-subscriber.workspace = true -------------------------------------------------------------------------------- /examples/poem/sse/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-sse" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["sse"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | futures-util.workspace = true 11 | tokio-stream.workspace = true 12 | tracing-subscriber.workspace = true 13 | -------------------------------------------------------------------------------- /examples/poem/mongodb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-mongodb" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | serde.workspace = true 11 | serde_json.workspace = true 12 | mongodb = "2" 13 | futures = "0.3" 14 | tracing-subscriber.workspace = true 15 | -------------------------------------------------------------------------------- /poem-openapi/src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "openapi-explorer")] 2 | pub(crate) mod openapi_explorer; 3 | #[cfg(feature = "rapidoc")] 4 | pub(crate) mod rapidoc; 5 | #[cfg(feature = "redoc")] 6 | pub(crate) mod redoc; 7 | #[cfg(feature = "scalar")] 8 | pub(crate) mod scalar; 9 | #[cfg(feature = "stoplight-elements")] 10 | pub(crate) mod stoplight_elements; 11 | #[cfg(feature = "swagger-ui")] 12 | pub(crate) mod swagger_ui; 13 | -------------------------------------------------------------------------------- /examples/openapi/custom-payload/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-custom-payload" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | bcs = "0.1.3" 9 | mime.workspace = true 10 | poem.workspace = true 11 | poem-openapi.workspace = true 12 | serde.workspace = true 13 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] } 14 | 15 | -------------------------------------------------------------------------------- /examples/openapi/sse/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-sse" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | async-stream = "0.3.2" 9 | futures-util.workspace = true 10 | poem.workspace = true 11 | poem-openapi.workspace = true 12 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] } 13 | tracing-subscriber.workspace = true 14 | -------------------------------------------------------------------------------- /examples/openapi/todos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poem-todos" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | sqlx = { version = "0.8.3", features = ["runtime-tokio-rustls", "sqlite"] } 11 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 12 | tokio-stream.workspace = true 13 | -------------------------------------------------------------------------------- /examples/openapi/content-type-accept/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-content-type-accept" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | bcs = "0.1.3" 9 | mime.workspace = true 10 | poem.workspace = true 11 | poem-openapi.workspace = true 12 | serde.workspace = true 13 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] } 14 | -------------------------------------------------------------------------------- /examples/grpc/helloworld_typename/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-grpc-helloworld-typename" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-grpc.workspace = true 10 | prost.workspace = true 11 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 12 | 13 | [build-dependencies] 14 | poem-grpc-build.workspace = true 15 | -------------------------------------------------------------------------------- /examples/poem/worker-hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples-worker-hello-world" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [workspace] 7 | 8 | [package.metadata.release] 9 | release = false 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | 14 | [dependencies] 15 | worker = { version = "0.6.0" } 16 | poem = { path = "../../../poem", default-features = false } 17 | poem-worker = { path = "../../../poem-worker" } 18 | -------------------------------------------------------------------------------- /examples/openapi/users-crud/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-users-crud" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi = { workspace = true, features = ["swagger-ui", "email"] } 10 | slab = "0.4.4" 11 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] } 12 | tracing-subscriber.workspace = true 13 | -------------------------------------------------------------------------------- /examples/poem/graceful-shutdown/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-graceful-shutdown" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["sse"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal"] } 10 | futures-util.workspace = true 11 | tokio-stream.workspace = true 12 | tracing-subscriber.workspace = true 13 | -------------------------------------------------------------------------------- /examples/poem/tower-layer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-tower-layer" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["tower-compat"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tower = { version = "0.4.8", default-features = false, features = ["limit"] } 11 | tracing-subscriber.workspace = true 12 | -------------------------------------------------------------------------------- /examples/poem/worker-hello-world/src/lib.rs: -------------------------------------------------------------------------------- 1 | use poem::{get, handler, web::Path, Route}; 2 | use poem_worker::{CloudflareProperties, Server}; 3 | use worker::event; 4 | 5 | #[handler] 6 | fn hello(Path(name): Path<String>, _cf: CloudflareProperties) -> String { 7 | format!("hello: {}", name) 8 | } 9 | 10 | #[event(start)] 11 | fn start() { 12 | let app = Route::new().at("/hello/:name", get(hello)); 13 | 14 | Server::new().run(app); 15 | } 16 | -------------------------------------------------------------------------------- /examples/openapi/auth-apikey/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-auth-apikey" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | jwt = "0.15.0" 9 | poem.workspace = true 10 | poem-openapi.workspace = true 11 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 12 | tracing-subscriber.workspace = true 13 | hmac = "0.11" 14 | sha2 = "0.9" 15 | serde.workspace = true 16 | -------------------------------------------------------------------------------- /examples/grpc/reflection/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-grpc-reflection" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-grpc.workspace = true 10 | prost.workspace = true 11 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 12 | tracing-subscriber.workspace = true 13 | 14 | [build-dependencies] 15 | poem-grpc-build.workspace = true 16 | -------------------------------------------------------------------------------- /examples/openapi/auth-multiple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-auth-multiple" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | jwt = "0.15.0" 9 | poem.workspace = true 10 | poem-openapi.workspace = true 11 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 12 | tracing-subscriber.workspace = true 13 | hmac = "0.11" 14 | sha2 = "0.9" 15 | serde.workspace = true 16 | -------------------------------------------------------------------------------- /poem-mcpserver/src/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | //! The protocol module contains all the necessary components to implement the 2 | //! MCP protocol. 3 | 4 | pub mod content; 5 | pub mod initialize; 6 | pub mod prompts; 7 | pub mod resources; 8 | pub mod rpc; 9 | pub mod tool; 10 | 11 | /// The JSON-RPC version. 12 | pub const JSON_RPC_VERSION: &str = "2.0"; 13 | 14 | /// The MCP protocol version. 15 | pub const MCP_PROTOCOL_VERSION: time::Date = time::macros::date!(2025 - 06 - 18); 16 | -------------------------------------------------------------------------------- /examples/openapi/auth-github/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-openapi-auth-github" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-openapi.workspace = true 10 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 11 | reqwest = { version = "0.12.2", default-features = false, features = [ 12 | "rustls-tls", 13 | ] } 14 | tracing-subscriber.workspace = true 15 | -------------------------------------------------------------------------------- /examples/mcpserver/counter-streamable-http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "counter-streamable-http" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | poem-mcpserver = { workspace = true, features = ["streamable-http"] } 8 | serde = { version = "1.0.219", features = ["derive"] } 9 | schemars = "1.0" 10 | poem = { workspace = true, features = ["sse"] } 11 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] } 12 | tracing-subscriber.workspace = true 13 | -------------------------------------------------------------------------------- /examples/poem/redis-session/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-redis-session" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["redis-session"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | redis = { version = "0.32", features = [ 12 | "aio", 13 | "tokio-comp", 14 | "connection-manager", 15 | ] } 16 | -------------------------------------------------------------------------------- /poem-openapi/src/ui/stoplight_elements/mod.rs: -------------------------------------------------------------------------------- 1 | use poem::{Endpoint, endpoint::make_sync, web::Html}; 2 | 3 | const TEMPLATE: &str = include_str!("stoplight-elements.html"); 4 | 5 | pub(crate) fn create_html(document: &str) -> String { 6 | TEMPLATE.replace("'{:spec}'", document) 7 | } 8 | 9 | pub(crate) fn create_endpoint(document: String) -> impl Endpoint { 10 | let ui_html = create_html(&document); 11 | poem::Route::new().at("/", make_sync(move |_| Html(ui_html.clone()))) 12 | } 13 | -------------------------------------------------------------------------------- /examples/grpc/helloworld/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-grpc-helloworld" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-grpc.workspace = true 10 | prost.workspace = true 11 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 12 | 13 | [build-dependencies] 14 | poem-grpc-build.workspace = true 15 | 16 | [[bin]] 17 | name = "grpc-helloworld-client" 18 | path = "src/client.rs" 19 | -------------------------------------------------------------------------------- /examples/grpc/middleware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-grpc-middleware" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-grpc.workspace = true 10 | prost.workspace = true 11 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 12 | 13 | [build-dependencies] 14 | poem-grpc-build.workspace = true 15 | 16 | [[bin]] 17 | name = "grpc-middleware-client" 18 | path = "src/client.rs" 19 | -------------------------------------------------------------------------------- /examples/grpc/helloworld_typename/src/main.rs: -------------------------------------------------------------------------------- 1 | use prost::Name; 2 | 3 | poem_grpc::include_proto!("helloworld"); 4 | 5 | fn main() -> Result<(), std::io::Error> { 6 | println!( 7 | "HelloRequest has {} full name and {} type url", 8 | HelloRequest::full_name(), 9 | HelloRequest::type_url() 10 | ); 11 | println!( 12 | "HelloReply has {} full name and {} type url", 13 | HelloReply::full_name(), 14 | HelloReply::type_url() 15 | ); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/grpc/jsoncodec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-grpc-jsoncodec" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-grpc = { workspace = true, features = ["json-codec"] } 10 | prost.workspace = true 11 | serde.workspace = true 12 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 13 | tracing-subscriber.workspace = true 14 | 15 | [build-dependencies] 16 | poem-grpc-build.workspace = true 17 | -------------------------------------------------------------------------------- /poem-grpc/src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Include generated server and client types. 2 | #[macro_export] 3 | macro_rules! include_proto { 4 | ($package: tt) => { 5 | include!(concat!(env!("OUT_DIR"), concat!("/", $package, ".rs"))); 6 | }; 7 | } 8 | 9 | /// Include an encoded `prost_types::FileDescriptorSet` as a `&'static [u8]`. 10 | #[macro_export] 11 | macro_rules! include_file_descriptor_set { 12 | ($package: tt) => { 13 | include_bytes!(concat!(env!("OUT_DIR"), concat!("/", $package))) 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /poem/src/listener/tls.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result as IoResult; 2 | 3 | use futures_util::Stream; 4 | 5 | /// Represents a type that can convert into tls config stream. 6 | #[cfg(any(feature = "rustls", feature = "native-tls", feature = "openssl-tls"))] 7 | pub trait IntoTlsConfigStream<C>: Send + 'static { 8 | /// Represents a tls config stream. 9 | type Stream: Stream<Item = C> + Send + 'static; 10 | 11 | /// Consume itself and return tls config stream. 12 | fn into_stream(self) -> IoResult<Self::Stream>; 13 | } 14 | -------------------------------------------------------------------------------- /examples/openapi/todos/README.md: -------------------------------------------------------------------------------- 1 | # TODOs Example 2 | 3 | ## Setup 4 | 5 | 1. Install sqlx-cli 6 | 7 | ```bash 8 | cargo install sqlx-cli --no-default-features --features sqlite 9 | ``` 10 | 11 | 2. Declare the database URL 12 | 13 | ```bash 14 | export DATABASE_URL="sqlite:todos.db" 15 | ``` 16 | 17 | 3. Create the database 18 | 19 | ```bash 20 | sqlx db create 21 | ``` 22 | 23 | 4. Run sql migrations 24 | 25 | ```bash 26 | sqlx migrate run 27 | ``` 28 | 29 | 5. Start the server 30 | 31 | ```bash 32 | cargo run 33 | ``` 34 | -------------------------------------------------------------------------------- /poem-grpc/proto/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grpc.health.v1; 4 | 5 | message HealthCheckRequest { string service = 1; } 6 | 7 | message HealthCheckResponse { 8 | enum ServingStatus { 9 | UNKNOWN = 0; 10 | SERVING = 1; 11 | NOT_SERVING = 2; 12 | SERVICE_UNKNOWN = 3; // Used only by the Watch method. 13 | } 14 | ServingStatus status = 1; 15 | } 16 | 17 | service Health { 18 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 19 | 20 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse); 21 | } -------------------------------------------------------------------------------- /examples/grpc/routeguide/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-grpc-routeguide" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | async-stream = "0.3.3" 9 | futures-util.workspace = true 10 | poem.workspace = true 11 | poem-grpc.workspace = true 12 | prost.workspace = true 13 | serde.workspace = true 14 | serde_json.workspace = true 15 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 16 | tracing-subscriber.workspace = true 17 | 18 | [build-dependencies] 19 | poem-grpc-build.workspace = true 20 | -------------------------------------------------------------------------------- /poem-mcpserver-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poem-mcpserver-macros" 3 | version = "0.3.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | documentation.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | rust-version.workspace = true 11 | description = "Macros for poem-mcpserver" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | darling.workspace = true 18 | proc-macro-crate.workspace = true 19 | proc-macro2.workspace = true 20 | quote.workspace = true 21 | syn.workspace = true 22 | -------------------------------------------------------------------------------- /examples/poem/lambda-hello-world/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{get, handler, web::Path, Route}; 2 | use poem_lambda::{run, Error}; 3 | 4 | #[handler] 5 | fn hello(Path(name): Path<String>) -> String { 6 | format!("hello: {name}") 7 | } 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<(), Error> { 11 | if std::env::var_os("RUST_LOG").is_none() { 12 | std::env::set_var("RUST_LOG", "poem=debug"); 13 | } 14 | tracing_subscriber::fmt::init(); 15 | 16 | let app = Route::new().at("/hello/:name", get(hello)); 17 | run(Route::new().nest("/prod", app)).await 18 | } 19 | -------------------------------------------------------------------------------- /poem-derive/src/utils.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_crate::{FoundCrate, crate_name}; 2 | use proc_macro2::{Span, TokenStream}; 3 | use quote::quote; 4 | use syn::Ident; 5 | 6 | pub(crate) fn get_crate_name(internal: bool) -> TokenStream { 7 | if internal { 8 | quote! { crate } 9 | } else { 10 | let name = match crate_name("poem") { 11 | Ok(FoundCrate::Name(name)) => name, 12 | Ok(FoundCrate::Itself) | Err(_) => "poem".to_string(), 13 | }; 14 | let name = Ident::new(&name, Span::call_site()); 15 | quote!(#name) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/disabled/tonic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-tonic" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["tower-compat"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | prost = "0.14" 11 | tonic = "0.14" 12 | tracing-subscriber.workspace = true 13 | tower = { version = "0.4.8", features = ["buffer"] } 14 | 15 | [build-dependencies] 16 | tonic-build = "0.14" 17 | 18 | [[bin]] 19 | name = "example-tonic-client" 20 | path = "src/client.rs" 21 | -------------------------------------------------------------------------------- /poem-grpc-build/src/utils.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_crate::{FoundCrate, crate_name}; 2 | use proc_macro2::{Span, TokenStream}; 3 | use quote::quote; 4 | use syn::Ident; 5 | 6 | pub(crate) fn get_crate_name(internal: bool) -> TokenStream { 7 | if internal { 8 | quote! { crate } 9 | } else { 10 | let name = match crate_name("poem-grpc") { 11 | Ok(FoundCrate::Name(name)) => name, 12 | Ok(FoundCrate::Itself) | Err(_) => "poem_grpc".to_string(), 13 | }; 14 | let name = Ident::new(&name, Span::call_site()); 15 | quote!(#name) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/poem/static-files/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{endpoint::StaticFilesEndpoint, listener::TcpListener, Route, Server}; 2 | 3 | #[tokio::main] 4 | async fn main() -> Result<(), std::io::Error> { 5 | if std::env::var_os("RUST_LOG").is_none() { 6 | std::env::set_var("RUST_LOG", "poem=debug"); 7 | } 8 | tracing_subscriber::fmt::init(); 9 | 10 | let app = Route::new().nest( 11 | "/", 12 | StaticFilesEndpoint::new("./poem/static-files/files").show_files_listing(), 13 | ); 14 | Server::new(TcpListener::bind("0.0.0.0:3000")) 15 | .run(app) 16 | .await 17 | } 18 | -------------------------------------------------------------------------------- /poem/src/endpoint/to_response.rs: -------------------------------------------------------------------------------- 1 | use crate::{Endpoint, Request, Response, Result}; 2 | 3 | /// Endpoint for the [`to_response`](super::EndpointExt::to_response) 4 | /// method. 5 | pub struct ToResponse<E> { 6 | inner: E, 7 | } 8 | 9 | impl<E> ToResponse<E> { 10 | #[inline] 11 | pub(crate) fn new(inner: E) -> ToResponse<E> { 12 | Self { inner } 13 | } 14 | } 15 | 16 | impl<E: Endpoint> Endpoint for ToResponse<E> { 17 | type Output = Response; 18 | 19 | async fn call(&self, req: Request) -> Result<Self::Output> { 20 | Ok(self.inner.get_response(req).await) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /poem-grpc-build/src/service_generator.rs: -------------------------------------------------------------------------------- 1 | use prost_build::{Service, ServiceGenerator}; 2 | 3 | use crate::config::GrpcConfig; 4 | 5 | pub(crate) struct PoemServiceGenerator { 6 | pub(crate) config: GrpcConfig, 7 | } 8 | 9 | impl ServiceGenerator for PoemServiceGenerator { 10 | fn generate(&mut self, service: Service, buf: &mut String) { 11 | if self.config.build_client { 12 | crate::client::generate(&self.config, &service, buf); 13 | } 14 | if self.config.build_server { 15 | crate::server::generate(&self.config, &service, buf); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/disabled/tonic/src/client.rs: -------------------------------------------------------------------------------- 1 | use hello_world::{greeter_client::GreeterClient, HelloRequest}; 2 | 3 | pub mod hello_world { 4 | tonic::include_proto!("helloworld"); 5 | } 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { 9 | let mut client = GreeterClient::connect("http://localhost:3000") 10 | .await 11 | .unwrap(); 12 | let request = tonic::Request::new(HelloRequest { 13 | name: "Tonic".into(), 14 | }); 15 | let response = client.say_hello(request).await?; 16 | println!("RESPONSE={response:?}"); 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/grpc/helloworld_compressed/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-grpc-helloworld-compressed" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem.workspace = true 9 | poem-grpc = { workspace = true, features = [ 10 | "gzip", 11 | "deflate", 12 | "brotli", 13 | "zstd", 14 | ] } 15 | prost.workspace = true 16 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 17 | 18 | [build-dependencies] 19 | poem-grpc-build.workspace = true 20 | 21 | [[bin]] 22 | name = "grpc-helloworld-compressed-client" 23 | path = "src/client.rs" 24 | -------------------------------------------------------------------------------- /examples/grpc/helloworld/src/client.rs: -------------------------------------------------------------------------------- 1 | use poem_grpc::{ClientConfig, Request}; 2 | 3 | poem_grpc::include_proto!("helloworld"); 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { 7 | let client = GreeterClient::new( 8 | ClientConfig::builder() 9 | .uri("http://localhost:3000") 10 | .build() 11 | .unwrap(), 12 | ); 13 | let request = Request::new(HelloRequest { 14 | name: "Poem".into(), 15 | }); 16 | let response = client.say_hello(request).await?; 17 | println!("RESPONSE={response:?}"); 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /examples/poem/nested-routing/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{get, handler, listener::TcpListener, Route, Server}; 2 | 3 | #[handler] 4 | fn hello() -> String { 5 | "hello".to_string() 6 | } 7 | 8 | fn api() -> Route { 9 | Route::new().at("/hello", get(hello)) 10 | } 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<(), std::io::Error> { 14 | if std::env::var_os("RUST_LOG").is_none() { 15 | std::env::set_var("RUST_LOG", "poem=debug") 16 | } 17 | tracing_subscriber::fmt::init(); 18 | 19 | let app = Route::new().nest("/api", api()); 20 | Server::new(TcpListener::bind("0.0.0.0:3000")) 21 | .run(app) 22 | .await 23 | } 24 | -------------------------------------------------------------------------------- /poem-openapi-derive/src/error.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub(crate) enum GeneratorError { 6 | #[error("{0}")] 7 | Syn(#[from] syn::Error), 8 | 9 | #[error("{0}")] 10 | Darling(#[from] darling::Error), 11 | } 12 | 13 | impl GeneratorError { 14 | pub(crate) fn write_errors(self) -> TokenStream { 15 | match self { 16 | GeneratorError::Syn(err) => err.to_compile_error(), 17 | GeneratorError::Darling(err) => err.write_errors(), 18 | } 19 | } 20 | } 21 | 22 | pub(crate) type GeneratorResult<T> = std::result::Result<T, GeneratorError>; 23 | -------------------------------------------------------------------------------- /examples/grpc/middleware/src/client.rs: -------------------------------------------------------------------------------- 1 | mod middleware; 2 | 3 | use poem_grpc::{ClientConfig, Request}; 4 | 5 | poem_grpc::include_proto!("helloworld"); 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { 9 | let client = GreeterClient::new( 10 | ClientConfig::builder() 11 | .uri("http://localhost:3000") 12 | .build() 13 | .unwrap(), 14 | ); 15 | let request = Request::new(HelloRequest { 16 | name: "Poem".into(), 17 | }); 18 | let response = client.say_hello(request).await?; 19 | println!("RESPONSE={response:?}"); 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /poem/src/endpoint/map_to_response.rs: -------------------------------------------------------------------------------- 1 | use crate::{Endpoint, IntoResponse, Request, Response, Result}; 2 | 3 | /// Endpoint for the [`map_to_response`](super::EndpointExt::map_to_response) 4 | /// method. 5 | pub struct MapToResponse<E> { 6 | inner: E, 7 | } 8 | 9 | impl<E> MapToResponse<E> { 10 | #[inline] 11 | pub(crate) fn new(inner: E) -> MapToResponse<E> { 12 | Self { inner } 13 | } 14 | } 15 | 16 | impl<E: Endpoint> Endpoint for MapToResponse<E> { 17 | type Output = Response; 18 | 19 | async fn call(&self, req: Request) -> Result<Self::Output> { 20 | self.inner.call(req).await.map(IntoResponse::into_response) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /poem-grpc/proto/test_harness.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test_harness; 4 | 5 | message UnaryRequest { 6 | int32 a = 1; 7 | int32 b = 2; 8 | } 9 | 10 | message ValueResponse { int32 value = 1; } 11 | 12 | message ValueRequest { int32 value = 1; } 13 | 14 | service TestHarness { 15 | rpc Unary(UnaryRequest) returns (ValueResponse); 16 | 17 | rpc ClientStreaming(stream ValueRequest) returns (ValueResponse); 18 | 19 | rpc ServerStreaming(ValueRequest) returns (stream ValueResponse); 20 | 21 | rpc BidirectionalStreaming(stream ValueRequest) 22 | returns (stream ValueResponse); 23 | 24 | rpc UnaryMetadata(UnaryRequest) returns (ValueResponse); 25 | } 26 | -------------------------------------------------------------------------------- /examples/openapi/content-type-accept/README.md: -------------------------------------------------------------------------------- 1 | # Example: Content Type + Accept 2 | 3 | The purpose of this example is to demonstrate how to use the following two features of HTTP: 4 | 5 | - `Content-Type`: When a client sends the server a request, it can use this to tell it what type the data is. 6 | - `Accept`: When a client sends the server a request, it can use this to tell it what type of data it would like to receive as the response. 7 | 8 | This allows a client to, for example, submit a request as JSON, but expect the response as XML, assuming the server supports both types. 9 | 10 | To see what kind of spec this code produces, `cargo run` and then visit http://localhost:3000/spec. 11 | -------------------------------------------------------------------------------- /poem-grpc/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | 3 | fn main() -> Result<()> { 4 | poem_grpc_build::Config::new() 5 | .build_client(false) 6 | .internal() 7 | .file_descriptor_set_path("grpc-reflection.bin") 8 | .compile( 9 | &["proto/reflection.proto", "proto/health.proto"], 10 | &["proto/"], 11 | )?; 12 | 13 | // for test 14 | poem_grpc_build::Config::new() 15 | .internal() 16 | .compile(&["proto/test_harness.proto"], &["proto/"])?; 17 | 18 | // example 19 | poem_grpc_build::Config::new() 20 | .internal() 21 | .compile(&["src/example_generated/routeguide.proto"], &[] as &[&str])?; 22 | 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /poem-mcpserver/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! MCP Server implementation for Poem 2 | 3 | #![forbid(unsafe_code)] 4 | #![deny(unreachable_pub)] 5 | #![cfg_attr(docsrs, feature(doc_cfg))] 6 | #![warn(rustdoc::broken_intra_doc_links)] 7 | #![warn(missing_docs)] 8 | 9 | pub mod content; 10 | pub mod protocol; 11 | mod server; 12 | pub mod stdio; 13 | #[cfg(feature = "streamable-http")] 14 | #[cfg_attr(docsrs, doc(cfg(feature = "streamable-http")))] 15 | pub mod streamable_http; 16 | pub mod tool; 17 | pub use poem_mcpserver_macros::Tools; 18 | pub use schemars::JsonSchema; 19 | pub use server::McpServer; 20 | 21 | #[doc(hidden)] 22 | pub mod private { 23 | pub use serde_json; 24 | 25 | pub use crate::tool::IntoToolResponse; 26 | } 27 | -------------------------------------------------------------------------------- /examples/poem/hello-world/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, listener::TcpListener, middleware::Tracing, web::Path, EndpointExt, Route, Server, 3 | }; 4 | 5 | #[handler] 6 | fn hello(Path(name): Path<String>) -> String { 7 | format!("hello: {name}") 8 | } 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<(), std::io::Error> { 12 | if std::env::var_os("RUST_LOG").is_none() { 13 | std::env::set_var("RUST_LOG", "poem=debug"); 14 | } 15 | tracing_subscriber::fmt::init(); 16 | 17 | let app = Route::new().at("/hello/:name", get(hello)).with(Tracing); 18 | Server::new(TcpListener::bind("0.0.0.0:3000")) 19 | .name("hello-world") 20 | .run(app) 21 | .await 22 | } 23 | -------------------------------------------------------------------------------- /poem-worker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poem-worker" 3 | version = "0.1.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | documentation.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | rust-version.workspace = true 11 | 12 | [dependencies] 13 | serde = { workspace = true } 14 | worker = { version = "0.6.0", features = ["http"] } 15 | bytes = { workspace = true } 16 | http = { workspace = true } 17 | http-body = "1.0.1" 18 | http-body-util = "0.1.0" 19 | async-trait = "0.1.88" 20 | 21 | poem = { workspace = true, default-features = false } 22 | 23 | tokio = { workspace = true } 24 | 25 | [features] 26 | queue = ["worker/queue"] 27 | d1 = ["worker/d1"] 28 | -------------------------------------------------------------------------------- /examples/poem/opentelemetry-jaeger/README.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | First make sure you have a running version of the Jaeger instance you want to send data to: 4 | 5 | ```shell 6 | docker run -e COLLECTOR_OTLP_ENABLED=true -p 4317:4317 -p 4318:4318 -p16686:16686 -p14268:14268 jaegertracing/all-in-one:latest 7 | ``` 8 | 9 | Launch the servers: 10 | 11 | ```shell 12 | cargo run --bin example-opentelemetry-server1 13 | ``` 14 | 15 | ```shell 16 | cargo run --bin example-opentelemetry-server2 17 | ``` 18 | 19 | Send a request from the client: 20 | 21 | ```shell 22 | cargo run --bin example-opentelemetry-client 23 | ``` 24 | 25 | Open `http://localhost:16686/` in the browser, you will see the following picture. 26 | 27 | ![jaeger](jaeger.png) 28 | -------------------------------------------------------------------------------- /poem-openapi/src/ui/stoplight_elements/stoplight-elements.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8" /> 5 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 6 | <title>OpenAPI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /poem/src/session/mod.rs: -------------------------------------------------------------------------------- 1 | //! Session management. 2 | 3 | mod cookie_config; 4 | mod cookie_session; 5 | mod memory_storage; 6 | #[cfg(feature = "redis-session")] 7 | mod redis_storage; 8 | mod server_session; 9 | #[allow(clippy::module_inception)] 10 | mod session; 11 | mod session_storage; 12 | #[cfg(test)] 13 | pub(crate) mod test_harness; 14 | 15 | pub use cookie_config::{CookieConfig, CookieSecurity}; 16 | pub use cookie_session::{CookieSession, CookieSessionEndpoint}; 17 | pub use memory_storage::MemoryStorage; 18 | #[cfg(feature = "redis-session")] 19 | pub use redis_storage::RedisStorage; 20 | pub use server_session::{ServerSession, ServerSessionEndpoint}; 21 | pub use session::{Session, SessionStatus}; 22 | pub use session_storage::SessionStorage; 23 | -------------------------------------------------------------------------------- /examples/poem/catch-panic/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | handler, 3 | listener::TcpListener, 4 | middleware::{CatchPanic, Tracing}, 5 | EndpointExt, Route, Server, 6 | }; 7 | 8 | #[handler] 9 | fn index() { 10 | panic!("error!") 11 | } 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<(), std::io::Error> { 15 | if std::env::var_os("RUST_LOG").is_none() { 16 | std::env::set_var("RUST_LOG", "poem=debug"); 17 | } 18 | tracing_subscriber::fmt::init(); 19 | 20 | let app = Route::new() 21 | .at("/", index) 22 | .with(Tracing) 23 | .with(CatchPanic::new()); 24 | Server::new(TcpListener::bind("0.0.0.0:3000")) 25 | .name("hello-world") 26 | .run(app) 27 | .await 28 | } 29 | -------------------------------------------------------------------------------- /examples/poem/combined-listeners/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, 3 | listener::{Listener, TcpListener}, 4 | IntoResponse, Route, Server, 5 | }; 6 | 7 | #[handler] 8 | fn hello() -> impl IntoResponse { 9 | "hello" 10 | } 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<(), std::io::Error> { 14 | if std::env::var_os("RUST_LOG").is_none() { 15 | std::env::set_var("RUST_LOG", "poem=debug"); 16 | } 17 | tracing_subscriber::fmt::init(); 18 | 19 | let app = Route::new().at("/", get(hello)); 20 | let listener = TcpListener::bind("0.0.0.0:3000") 21 | .combine(TcpListener::bind("0.0.0.0:3001")) 22 | .combine(TcpListener::bind("0.0.0.0:3002")); 23 | Server::new(listener).run(app).await 24 | } 25 | -------------------------------------------------------------------------------- /poem-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poem-derive" 3 | version = "3.1.12" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | documentation.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | rust-version.workspace = true 11 | description = "Macros for poem" 12 | keywords = ["http", "web", "framework", "async"] 13 | categories = [ 14 | "network-programming", 15 | "asynchronous", 16 | "web-programming::http-server", 17 | "web-programming::websocket", 18 | ] 19 | 20 | [lib] 21 | proc-macro = true 22 | 23 | [dependencies] 24 | proc-macro-crate.workspace = true 25 | proc-macro2.workspace = true 26 | quote.workspace = true 27 | syn = { workspace = true, features = ["full"] } 28 | -------------------------------------------------------------------------------- /poem/src/endpoint/before.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use crate::{Endpoint, Request, Result}; 4 | 5 | /// Endpoint for the [`before`](super::EndpointExt::before) method. 6 | pub struct Before { 7 | inner: E, 8 | f: F, 9 | } 10 | 11 | impl Before { 12 | #[inline] 13 | pub(crate) fn new(inner: E, f: F) -> Before { 14 | Self { inner, f } 15 | } 16 | } 17 | 18 | impl Endpoint for Before 19 | where 20 | E: Endpoint, 21 | F: Fn(Request) -> Fut + Send + Sync, 22 | Fut: Future> + Send, 23 | { 24 | type Output = E::Output; 25 | 26 | async fn call(&self, req: Request) -> Result { 27 | self.inner.call((self.f)(req).await?).await 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/poem/unix-socket/src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | #[tokio::main] 3 | async fn main() -> Result<(), std::io::Error> { 4 | use poem::{get, handler, http::Uri, listener::UnixListener, IntoResponse, Route, Server}; 5 | 6 | #[handler] 7 | fn hello(uri: &Uri) -> impl IntoResponse { 8 | uri.path().to_string() 9 | } 10 | 11 | if std::env::var_os("RUST_LOG").is_none() { 12 | std::env::set_var("RUST_LOG", "poem=debug"); 13 | } 14 | tracing_subscriber::fmt::init(); 15 | 16 | let app = Route::new().at("/", get(hello)); 17 | let listener = UnixListener::bind("./unix-socket"); 18 | Server::new(listener).run(app).await 19 | } 20 | 21 | #[cfg(not(unix))] 22 | fn main() { 23 | println!("This example works only on Unix systems!"); 24 | } 25 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/max_length.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Display; 2 | 3 | use crate::{ 4 | registry::MetaSchema, 5 | validation::{Validator, ValidatorMeta}, 6 | }; 7 | 8 | #[derive(Display)] 9 | #[display("maxLength({len})")] 10 | pub struct MaxLength { 11 | len: usize, 12 | } 13 | 14 | impl MaxLength { 15 | #[inline] 16 | pub fn new(len: usize) -> Self { 17 | Self { len } 18 | } 19 | } 20 | 21 | impl> Validator for MaxLength { 22 | #[inline] 23 | fn check(&self, value: &T) -> bool { 24 | value.as_ref().chars().nth(self.len).is_none() 25 | } 26 | } 27 | 28 | impl ValidatorMeta for MaxLength { 29 | fn update_meta(&self, meta: &mut MetaSchema) { 30 | meta.max_length = Some(self.len); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/openapi/custom-payload/README.md: -------------------------------------------------------------------------------- 1 | # Custom Payload 2 | 3 | The purpose of this example is to show you how you can make your own custom payload "wrapper" types, similar to how you can accept and return JSON (for example). 4 | 5 | For the example we implement a custom payload using [BCS](https://docs.rs/bcs/latest/bcs/). This example could theoretically used something else, such as bincode or yaml, there is nothing special about BCS itself for the purposes of this example. 6 | 7 | ## Running this example 8 | Run the server: 9 | ``` 10 | cargo run 11 | ``` 12 | 13 | Send a request. We have included a file with data already in BCS format for this purpose: 14 | ``` 15 | curl -X POST localhost:3000/api/echo -H 'Content-Type: application/x-bcs' -H 'accept: application/x-bcs' -d "@data.bcs" 16 | ``` 17 | -------------------------------------------------------------------------------- /examples/poem/embed-files/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | endpoint::{EmbeddedFileEndpoint, EmbeddedFilesEndpoint}, 3 | listener::TcpListener, 4 | Route, Server, 5 | }; 6 | use rust_embed::RustEmbed; 7 | 8 | #[derive(RustEmbed)] 9 | #[folder = "files"] 10 | pub struct Files; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<(), std::io::Error> { 14 | if std::env::var_os("RUST_LOG").is_none() { 15 | std::env::set_var("RUST_LOG", "poem=debug"); 16 | } 17 | tracing_subscriber::fmt::init(); 18 | 19 | let app = Route::new() 20 | .at("/", EmbeddedFileEndpoint::::new("index.html")) 21 | .nest("/files", EmbeddedFilesEndpoint::::new()); 22 | Server::new(TcpListener::bind("0.0.0.0:3000")) 23 | .run(app) 24 | .await 25 | } 26 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/multiple_of.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Display; 2 | use num_traits::AsPrimitive; 3 | 4 | use crate::{ 5 | registry::MetaSchema, 6 | validation::{Validator, ValidatorMeta}, 7 | }; 8 | 9 | #[derive(Display)] 10 | #[display("multipleOf({n})")] 11 | pub struct MultipleOf { 12 | n: f64, 13 | } 14 | 15 | impl MultipleOf { 16 | #[inline] 17 | pub fn new(n: f64) -> Self { 18 | Self { n } 19 | } 20 | } 21 | 22 | impl> Validator for MultipleOf { 23 | #[inline] 24 | fn check(&self, value: &T) -> bool { 25 | value.as_() % self.n == 0.0 26 | } 27 | } 28 | 29 | impl ValidatorMeta for MultipleOf { 30 | fn update_meta(&self, meta: &mut MetaSchema) { 31 | meta.multiple_of = Some(self.n); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/min_length.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Display; 2 | 3 | use crate::{ 4 | registry::MetaSchema, 5 | validation::{Validator, ValidatorMeta}, 6 | }; 7 | 8 | #[derive(Display)] 9 | #[display("minLength({len})")] 10 | pub struct MinLength { 11 | len: usize, 12 | } 13 | 14 | impl MinLength { 15 | #[inline] 16 | pub fn new(len: usize) -> Self { 17 | Self { len } 18 | } 19 | } 20 | 21 | impl> Validator for MinLength { 22 | #[inline] 23 | fn check(&self, value: &T) -> bool { 24 | self.len == 0 || value.as_ref().chars().nth(self.len - 1).is_some() 25 | } 26 | } 27 | 28 | impl ValidatorMeta for MinLength { 29 | fn update_meta(&self, meta: &mut MetaSchema) { 30 | meta.min_length = Some(self.len); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /poem-lambda/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poem-lambda" 3 | version = "5.1.4" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | documentation.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | rust-version.workspace = true 11 | readme = "README.md" 12 | description = "Poem for AWS Lambda" 13 | keywords = ["http", "web", "framework", "async"] 14 | categories = [ 15 | "network-programming", 16 | "asynchronous", 17 | "web-programming::http-server", 18 | "web-programming::websocket", 19 | ] 20 | 21 | [dependencies] 22 | poem = { workspace = true, default-features = false } 23 | 24 | lambda_http = { version = "0.16.0" } 25 | 26 | [dev-dependencies] 27 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 28 | -------------------------------------------------------------------------------- /poem-openapi/src/docs/request.md: -------------------------------------------------------------------------------- 1 | Define an OpenAPI request. 2 | 3 | # Item parameters 4 | 5 | | Attribute | Description | Type | Optional | 6 | |--------------|---------------------------|--------|----------| 7 | | content_type | Specify the content type. | string | Y | 8 | 9 | # Examples 10 | 11 | ```rust 12 | use poem_openapi::{ 13 | payload::{Json, PlainText}, 14 | ApiRequest, Object, 15 | }; 16 | 17 | #[derive(Object)] 18 | struct Pet { 19 | id: String, 20 | name: String, 21 | } 22 | 23 | #[derive(ApiRequest)] 24 | enum CreatePet { 25 | /// This request receives a pet in JSON format(application/json). 26 | CreateByJSON(Json), 27 | /// This request receives a pet in text format(text/plain). 28 | CreateByPlainText(PlainText), 29 | } 30 | ``` -------------------------------------------------------------------------------- /poem/src/endpoint/after.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use crate::{Endpoint, IntoResponse, Request, Result}; 4 | 5 | /// Endpoint for the [`after`](super::EndpointExt::after) method. 6 | pub struct After { 7 | inner: E, 8 | f: F, 9 | } 10 | 11 | impl After { 12 | #[inline] 13 | pub(crate) fn new(inner: E, f: F) -> After { 14 | Self { inner, f } 15 | } 16 | } 17 | 18 | impl Endpoint for After 19 | where 20 | E: Endpoint, 21 | F: Fn(Result) -> Fut + Send + Sync, 22 | Fut: Future> + Send, 23 | T: IntoResponse, 24 | { 25 | type Output = T; 26 | 27 | async fn call(&self, req: Request) -> Result { 28 | (self.f)(self.inner.call(req).await).await 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/grpc/helloworld_compressed/src/client.rs: -------------------------------------------------------------------------------- 1 | use poem_grpc::{ClientConfig, CompressionEncoding, Request}; 2 | 3 | poem_grpc::include_proto!("helloworld"); 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Box> { 7 | let mut client = GreeterClient::new( 8 | ClientConfig::builder() 9 | .uri("http://localhost:3000") 10 | .build() 11 | .unwrap(), 12 | ); 13 | client.set_send_compressed(CompressionEncoding::GZIP); 14 | client.set_accept_compressed([CompressionEncoding::GZIP]); 15 | 16 | let request = Request::new(HelloRequest { 17 | name: "Poem".into(), 18 | }); 19 | let response = client.say_hello(request).await?; 20 | println!("RESPONSE={response:?}"); 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /examples/poem/tower-layer/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, listener::TcpListener, middleware::TowerLayerCompatExt, EndpointExt, Route, 3 | Server, 4 | }; 5 | use tokio::time::Duration; 6 | use tower::limit::RateLimitLayer; 7 | 8 | #[handler] 9 | fn hello() -> &'static str { 10 | "hello" 11 | } 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<(), std::io::Error> { 15 | if std::env::var_os("RUST_LOG").is_none() { 16 | std::env::set_var("RUST_LOG", "poem=debug"); 17 | } 18 | tracing_subscriber::fmt::init(); 19 | 20 | let app = Route::new().at( 21 | "/", 22 | get(hello).with(RateLimitLayer::new(5, Duration::from_secs(30)).compat()), 23 | ); 24 | Server::new(TcpListener::bind("0.0.0.0:3000")) 25 | .run(app) 26 | .await 27 | } 28 | -------------------------------------------------------------------------------- /poem-grpc-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poem-grpc-build" 3 | version = "0.5.9" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | documentation.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | rust-version.workspace = true 11 | description = "Codegen module of poem-grpc." 12 | keywords = ["http", "async", "grpc"] 13 | categories = ["network-programming", "asynchronous"] 14 | 15 | [dependencies] 16 | prettyplease = "0.2.9" 17 | prost-build = "0.14" 18 | quote.workspace = true 19 | proc-macro2.workspace = true 20 | syn.workspace = true 21 | proc-macro-crate.workspace = true 22 | 23 | [package.metadata.workspaces] 24 | independent = true 25 | 26 | [package.metadata.docs.rs] 27 | all-features = true 28 | rustdoc-args = ["--cfg", "docsrs"] 29 | -------------------------------------------------------------------------------- /poem-grpc/src/streaming.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | pin::Pin, 3 | task::{Context, Poll}, 4 | }; 5 | 6 | use futures_util::{Stream, StreamExt, stream::BoxStream}; 7 | 8 | use crate::Status; 9 | 10 | /// Message stream 11 | pub struct Streaming(BoxStream<'static, Result>); 12 | 13 | impl Streaming { 14 | /// Create a message stream 15 | #[inline] 16 | pub fn new(stream: S) -> Self 17 | where 18 | S: Stream> + Send + 'static, 19 | { 20 | Self(stream.boxed()) 21 | } 22 | } 23 | 24 | impl Stream for Streaming { 25 | type Item = Result; 26 | 27 | #[inline] 28 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 29 | self.0.poll_next_unpin(cx) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /poem-openapi/src/auth/bearer.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | Request, Result, 3 | web::headers::{Authorization, HeaderMapExt}, 4 | }; 5 | 6 | use crate::{auth::BearerAuthorization, error::AuthorizationError}; 7 | 8 | /// Used to extract the token68 from the request. 9 | #[derive(Debug)] 10 | pub struct Bearer { 11 | /// token 12 | pub token: String, 13 | } 14 | 15 | impl BearerAuthorization for Bearer { 16 | fn from_request(req: &Request) -> Result { 17 | if let Some(auth) = req 18 | .headers() 19 | .typed_get::>() 20 | { 21 | return Ok(Bearer { 22 | token: auth.token().to_string(), 23 | }); 24 | } 25 | 26 | Err(AuthorizationError.into()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/poem/json/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{handler, listener::TcpListener, post, web::Json, Route, Server}; 2 | use serde::Deserialize; 3 | 4 | #[derive(Debug, Deserialize)] 5 | struct CreateSomething { 6 | name: String, 7 | } 8 | 9 | #[handler] 10 | fn hello(req: Json) -> Json { 11 | Json(serde_json::json! ({ 12 | "code": 0, 13 | "message": req.name, 14 | })) 15 | } 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<(), std::io::Error> { 19 | if std::env::var_os("RUST_LOG").is_none() { 20 | std::env::set_var("RUST_LOG", "poem=debug"); 21 | } 22 | tracing_subscriber::fmt::init(); 23 | 24 | let app = Route::new().at("/hello", post(hello)); 25 | Server::new(TcpListener::bind("0.0.0.0:3000")) 26 | .run(app) 27 | .await 28 | } 29 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/max_items.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use derive_more::Display; 4 | 5 | use crate::{ 6 | registry::MetaSchema, 7 | types::Type, 8 | validation::{Validator, ValidatorMeta}, 9 | }; 10 | 11 | #[derive(Display)] 12 | #[display("maxItems({len})")] 13 | pub struct MaxItems { 14 | len: usize, 15 | } 16 | 17 | impl MaxItems { 18 | #[inline] 19 | pub fn new(len: usize) -> Self { 20 | Self { len } 21 | } 22 | } 23 | 24 | impl, E: Type> Validator for MaxItems { 25 | #[inline] 26 | fn check(&self, value: &T) -> bool { 27 | value.deref().len() <= self.len 28 | } 29 | } 30 | 31 | impl ValidatorMeta for MaxItems { 32 | fn update_meta(&self, meta: &mut MetaSchema) { 33 | meta.max_items = Some(self.len); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/min_items.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use derive_more::Display; 4 | 5 | use crate::{ 6 | registry::MetaSchema, 7 | types::Type, 8 | validation::{Validator, ValidatorMeta}, 9 | }; 10 | 11 | #[derive(Display)] 12 | #[display("minItems({len})")] 13 | pub struct MinItems { 14 | len: usize, 15 | } 16 | 17 | impl MinItems { 18 | #[inline] 19 | pub fn new(len: usize) -> Self { 20 | Self { len } 21 | } 22 | } 23 | 24 | impl, E: Type> Validator for MinItems { 25 | #[inline] 26 | fn check(&self, value: &T) -> bool { 27 | value.deref().len() >= self.len 28 | } 29 | } 30 | 31 | impl ValidatorMeta for MinItems { 32 | fn update_meta(&self, meta: &mut MetaSchema) { 33 | meta.min_items = Some(self.len); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /poem-grpc-build/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Codegen module of `poem-grpc 2 | 3 | #![doc(html_favicon_url = "https://raw.githubusercontent.com/poem-web/poem/master/favicon.ico")] 4 | #![doc(html_logo_url = "https://raw.githubusercontent.com/poem-web/poem/master/logo.png")] 5 | #![forbid(unsafe_code)] 6 | #![deny(unreachable_pub)] 7 | #![cfg_attr(docsrs, feature(doc_cfg))] 8 | #![warn(missing_docs)] 9 | 10 | mod client; 11 | mod config; 12 | mod server; 13 | mod service_generator; 14 | mod utils; 15 | 16 | use std::path::Path; 17 | 18 | pub use config::Config; 19 | 20 | /// Compile .proto files into Rust files during a Cargo build with default 21 | /// options. 22 | pub fn compile_protos( 23 | protos: &[impl AsRef], 24 | includes: &[impl AsRef], 25 | ) -> std::io::Result<()> { 26 | Config::new().compile(protos, includes) 27 | } 28 | -------------------------------------------------------------------------------- /examples/grpc/helloworld/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, Server}; 2 | use poem_grpc::{Request, Response, RouteGrpc, Status}; 3 | 4 | poem_grpc::include_proto!("helloworld"); 5 | 6 | struct GreeterService; 7 | 8 | impl Greeter for GreeterService { 9 | async fn say_hello( 10 | &self, 11 | request: Request, 12 | ) -> Result, Status> { 13 | let reply = HelloReply { 14 | message: format!("Hello {}!", request.into_inner().name), 15 | }; 16 | Ok(Response::new(reply)) 17 | } 18 | } 19 | 20 | #[tokio::main] 21 | async fn main() -> Result<(), std::io::Error> { 22 | let route = RouteGrpc::new().add_service(GreeterServer::new(GreeterService)); 23 | Server::new(TcpListener::bind("0.0.0.0:3000")) 24 | .run(route) 25 | .await 26 | } 27 | -------------------------------------------------------------------------------- /examples/grpc/middleware/src/main.rs: -------------------------------------------------------------------------------- 1 | mod middleware; 2 | 3 | use poem::{listener::TcpListener, Server}; 4 | use poem_grpc::{Request, Response, RouteGrpc, Status}; 5 | 6 | poem_grpc::include_proto!("helloworld"); 7 | 8 | struct GreeterService; 9 | 10 | impl Greeter for GreeterService { 11 | async fn say_hello( 12 | &self, 13 | request: Request, 14 | ) -> Result, Status> { 15 | let reply = HelloReply { 16 | message: format!("Hello {}!", request.into_inner().name), 17 | }; 18 | Ok(Response::new(reply)) 19 | } 20 | } 21 | 22 | #[tokio::main] 23 | async fn main() -> Result<(), std::io::Error> { 24 | Server::new(TcpListener::bind("0.0.0.0:3000")) 25 | .run(RouteGrpc::new().add_service(GreeterServer::new(GreeterService))) 26 | .await 27 | } 28 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/pattern.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Display; 2 | use regex::Regex; 3 | 4 | use crate::{ 5 | registry::MetaSchema, 6 | validation::{Validator, ValidatorMeta}, 7 | }; 8 | 9 | #[derive(Display)] 10 | #[display("pattern(\"{pattern}\")")] 11 | pub struct Pattern { 12 | pattern: &'static str, 13 | } 14 | 15 | impl Pattern { 16 | #[inline] 17 | pub fn new(pattern: &'static str) -> Self { 18 | Self { pattern } 19 | } 20 | } 21 | 22 | impl> Validator for Pattern { 23 | #[inline] 24 | fn check(&self, value: &T) -> bool { 25 | Regex::new(self.pattern).unwrap().is_match(value.as_ref()) 26 | } 27 | } 28 | 29 | impl ValidatorMeta for Pattern { 30 | fn update_meta(&self, meta: &mut MetaSchema) { 31 | meta.pattern = Some(self.pattern.to_string()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /poem/src/endpoint/map.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use crate::{Endpoint, IntoResponse, Request, Result}; 4 | 5 | /// Endpoint for the [`map_ok`](super::EndpointExt::map) method. 6 | pub struct Map { 7 | inner: E, 8 | f: F, 9 | } 10 | 11 | impl Map { 12 | #[inline] 13 | pub(crate) fn new(inner: E, f: F) -> Map { 14 | Self { inner, f } 15 | } 16 | } 17 | 18 | impl Endpoint for Map 19 | where 20 | E: Endpoint, 21 | F: Fn(R) -> Fut + Send + Sync, 22 | Fut: Future + Send, 23 | R: IntoResponse, 24 | R2: IntoResponse, 25 | { 26 | type Output = R2; 27 | 28 | async fn call(&self, req: Request) -> Result { 29 | let resp = self.inner.call(req).await?; 30 | Ok((self.f)(resp).await) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /poem-mcpserver/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | # [0.3.0] - 2025-11-11 8 | 9 | - implement `IntoToolResponse` for `()` 10 | 11 | # [0.2.9] - 2025-10-10 12 | 13 | - bump `schemars` to 1.0 14 | - add support for output schema in tools. 15 | 16 | # [0.2.4] 20250-06-06 17 | 18 | - bump `schemars` to 0.9 19 | - Fix typo [#1030](https://github.com/poem-web/poem/pull/1030) 20 | 21 | # [0.2.3] 2025-05-07 22 | 23 | - add `McpServer::disable_tools` method to disable specific tools. 24 | 25 | # [0.2.2] 2025-05-03 26 | 27 | - add with_server_info builder method [#1015](https://github.com/poem-web/poem/pull/1015) 28 | -------------------------------------------------------------------------------- /poem-openapi/src/docs/response_content.md: -------------------------------------------------------------------------------- 1 | Define an OpenAPI response content. 2 | 3 | # Item parameters 4 | 5 | | Attribute | Description | Type | Optional | 6 | |--------------|------------------------------------|--------|----------| 7 | | content_type | Specify the content type. | string | Y | 8 | | actual_type | Specifies the actual response type | string | Y | 9 | 10 | # Examples 11 | 12 | ```rust 13 | use poem_openapi::{ 14 | payload::{Binary, Json, PlainText}, 15 | ApiResponse, ResponseContent, 16 | }; 17 | 18 | #[derive(ResponseContent)] 19 | enum MyResponseContent { 20 | A(Json), 21 | B(PlainText), 22 | C(Binary>), 23 | } 24 | 25 | #[derive(ApiResponse)] 26 | enum MyResponse { 27 | #[oai(status = 200)] 28 | Ok(MyResponseContent), 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /poem-grpc/src/route.rs: -------------------------------------------------------------------------------- 1 | use poem::{IntoEndpoint, Response, endpoint::BoxEndpoint}; 2 | 3 | use crate::Service; 4 | 5 | /// A router for GRPC services 6 | #[derive(Default)] 7 | pub struct RouteGrpc { 8 | route: poem::Route, 9 | } 10 | 11 | impl RouteGrpc { 12 | /// Create a `GrpcRoute` 13 | pub fn new() -> Self { 14 | Default::default() 15 | } 16 | 17 | /// Add a GRPC service 18 | pub fn add_service(mut self, service: S) -> Self 19 | where 20 | S: IntoEndpoint> + Service, 21 | { 22 | self.route = self.route.nest(format!("/{}", S::NAME), service); 23 | self 24 | } 25 | } 26 | 27 | impl IntoEndpoint for RouteGrpc { 28 | type Endpoint = poem::Route; 29 | 30 | fn into_endpoint(self) -> Self::Endpoint { 31 | self.route 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/grpc/routeguide/src/data.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Debug, Deserialize)] 4 | struct Feature { 5 | location: Location, 6 | name: String, 7 | } 8 | 9 | #[derive(Debug, Deserialize)] 10 | struct Location { 11 | latitude: i32, 12 | longitude: i32, 13 | } 14 | 15 | #[allow(dead_code)] 16 | pub fn load() -> Vec { 17 | let decoded: Vec = serde_json::from_slice(include_bytes!("route_guide_db.json")) 18 | .expect("failed to deserialize features"); 19 | 20 | decoded 21 | .into_iter() 22 | .map(|feature| crate::Feature { 23 | name: feature.name, 24 | location: Some(crate::Point { 25 | longitude: feature.location.longitude, 26 | latitude: feature.location.latitude, 27 | }), 28 | }) 29 | .collect() 30 | } 31 | -------------------------------------------------------------------------------- /poem/src/endpoint/and_then.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use crate::{Endpoint, IntoResponse, Request, Result}; 4 | 5 | /// Endpoint for the [`and_then`](super::EndpointExt::and_then) method. 6 | pub struct AndThen { 7 | inner: E, 8 | f: F, 9 | } 10 | 11 | impl AndThen { 12 | #[inline] 13 | pub(crate) fn new(inner: E, f: F) -> AndThen { 14 | Self { inner, f } 15 | } 16 | } 17 | 18 | impl Endpoint for AndThen 19 | where 20 | E: Endpoint, 21 | F: Fn(R) -> Fut + Send + Sync, 22 | Fut: Future> + Send, 23 | R: IntoResponse, 24 | R2: IntoResponse, 25 | { 26 | type Output = R2; 27 | 28 | async fn call(&self, req: Request) -> Result { 29 | let resp = self.inner.call(req).await?; 30 | (self.f)(resp).await 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /poem/src/listener/certs/openssl.cnf: -------------------------------------------------------------------------------- 1 | 2 | [ v3_end ] 3 | basicConstraints = critical,CA:false 4 | keyUsage = nonRepudiation, digitalSignature 5 | subjectKeyIdentifier = hash 6 | authorityKeyIdentifier = keyid:always,issuer:always 7 | subjectAltName = @alt_names 8 | 9 | [ v3_client ] 10 | basicConstraints = critical,CA:false 11 | keyUsage = nonRepudiation, digitalSignature 12 | extendedKeyUsage = critical, clientAuth 13 | subjectKeyIdentifier = hash 14 | authorityKeyIdentifier = keyid:always,issuer:always 15 | 16 | [ v3_inter ] 17 | subjectKeyIdentifier = hash 18 | extendedKeyUsage = critical, serverAuth, clientAuth 19 | basicConstraints = CA:true 20 | keyUsage = cRLSign, keyCertSign, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign 21 | 22 | [ alt_names ] 23 | DNS.1 = testserver.com 24 | DNS.2 = second.testserver.com 25 | DNS.3 = localhost 26 | -------------------------------------------------------------------------------- /poem/src/endpoint/around.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, sync::Arc}; 2 | 3 | use crate::{Endpoint, IntoResponse, Request, Result}; 4 | 5 | /// Endpoint for the [`around`](super::EndpointExt::around) method. 6 | pub struct Around { 7 | inner: Arc, 8 | f: F, 9 | } 10 | 11 | impl Around { 12 | #[inline] 13 | pub(crate) fn new(inner: E, f: F) -> Around { 14 | Self { 15 | inner: Arc::new(inner), 16 | f, 17 | } 18 | } 19 | } 20 | 21 | impl Endpoint for Around 22 | where 23 | E: Endpoint, 24 | F: Fn(Arc, Request) -> Fut + Send + Sync + 'static, 25 | Fut: Future> + Send, 26 | T: IntoResponse, 27 | { 28 | type Output = T; 29 | 30 | async fn call(&self, req: Request) -> Result { 31 | (self.f)(self.inner.clone(), req).await 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /poem/src/endpoint/inspect_all_err.rs: -------------------------------------------------------------------------------- 1 | use crate::{Endpoint, Error, Request, Result}; 2 | 3 | /// Endpoint for the [`inspect_all_err`](super::EndpointExt::inspect_all_err) 4 | /// method. 5 | pub struct InspectAllError { 6 | inner: E, 7 | f: F, 8 | } 9 | 10 | impl InspectAllError { 11 | #[inline] 12 | pub(crate) fn new(inner: E, f: F) -> InspectAllError { 13 | Self { inner, f } 14 | } 15 | } 16 | 17 | impl Endpoint for InspectAllError 18 | where 19 | E: Endpoint, 20 | F: Fn(&Error) + Send + Sync, 21 | { 22 | type Output = E::Output; 23 | 24 | async fn call(&self, req: Request) -> Result { 25 | match self.inner.call(req).await { 26 | Ok(resp) => Ok(resp), 27 | Err(err) => { 28 | (self.f)(&err); 29 | Err(err) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["poem/*", "openapi/*", "grpc/*", "mcpserver/*"] 4 | exclude = ["poem/worker-hello-world"] 5 | 6 | [workspace.package] 7 | version = "0.1.0" 8 | edition = "2021" 9 | publish = false 10 | 11 | [workspace.dependencies] 12 | poem = { path = "../poem" } 13 | poem-grpc = { path = "../poem-grpc" } 14 | poem-openapi = { path = "../poem-openapi", features = ["swagger-ui"] } 15 | poem-lambda = { path = "../poem-lambda" } 16 | poem-grpc-build = { path = "../poem-grpc-build" } 17 | poem-mcpserver = { path = "../poem-mcpserver" } 18 | poem-worker = { path = "../poem-worker" } 19 | 20 | tokio = "1.17.0" 21 | tracing-subscriber = { version = "0.3.9", features = ["env-filter"] } 22 | serde_json = "1.0.68" 23 | serde = { version = "1.0.140", features = ["derive"] } 24 | mime = "0.3.16" 25 | open = "5.0.1" 26 | futures-util = "0.3.21" 27 | tokio-stream = "0.1.8" 28 | prost = "0.14" 29 | -------------------------------------------------------------------------------- /poem-openapi-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poem-openapi-derive" 3 | version = "5.1.16" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | documentation.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | rust-version.workspace = true 11 | description = "Macros for poem-openapi" 12 | keywords = ["http", "async", "openapi", "swagger"] 13 | categories = ["network-programming", "asynchronous"] 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | darling.workspace = true 20 | proc-macro-crate.workspace = true 21 | proc-macro2.workspace = true 22 | quote.workspace = true 23 | syn = { workspace = true, features = ["full", "visit-mut"] } 24 | thiserror.workspace = true 25 | indexmap.workspace = true 26 | regex.workspace = true 27 | http.workspace = true 28 | mime.workspace = true 29 | 30 | [package.metadata.workspaces] 31 | independent = true 32 | -------------------------------------------------------------------------------- /examples/poem/cookie-session/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, 3 | listener::TcpListener, 4 | session::{CookieConfig, CookieSession, Session}, 5 | EndpointExt, Route, Server, 6 | }; 7 | 8 | #[handler] 9 | async fn count(session: &Session) -> String { 10 | let count = session.get::("count").unwrap_or(0) + 1; 11 | session.set("count", count); 12 | format!("Hello!\nHow many times have seen you: {count}") 13 | } 14 | 15 | #[tokio::main] 16 | async fn main() -> Result<(), std::io::Error> { 17 | if std::env::var_os("RUST_LOG").is_none() { 18 | std::env::set_var("RUST_LOG", "poem=debug"); 19 | } 20 | tracing_subscriber::fmt::init(); 21 | 22 | let app = Route::new() 23 | .at("/", get(count)) 24 | .with(CookieSession::new(CookieConfig::default().secure(false))); 25 | Server::new(TcpListener::bind("0.0.0.0:3000")) 26 | .run(app) 27 | .await 28 | } 29 | -------------------------------------------------------------------------------- /poem/src/session/session_storage.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, future::Future, time::Duration}; 2 | 3 | use serde_json::Value; 4 | 5 | use crate::Result; 6 | 7 | /// Represents a back-end session storage. 8 | pub trait SessionStorage: Send + Sync { 9 | /// Load session entries. 10 | fn load_session<'a>( 11 | &'a self, 12 | session_id: &'a str, 13 | ) -> impl Future>>> + Send + 'a; 14 | 15 | /// Insert or update a session. 16 | fn update_session<'a>( 17 | &'a self, 18 | session_id: &'a str, 19 | entries: &'a BTreeMap, 20 | expires: Option, 21 | ) -> impl Future> + Send + 'a; 22 | 23 | /// Remove a session by session id. 24 | fn remove_session<'a>( 25 | &'a self, 26 | session_id: &'a str, 27 | ) -> impl Future> + Send + 'a; 28 | } 29 | -------------------------------------------------------------------------------- /poem-openapi/src/auth/basic.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | Request, Result, 3 | web::headers::{Authorization, HeaderMapExt}, 4 | }; 5 | 6 | use crate::{auth::BasicAuthorization, error::AuthorizationError}; 7 | 8 | /// Used to extract the username/password from the request. 9 | #[derive(Debug)] 10 | pub struct Basic { 11 | /// username 12 | pub username: String, 13 | 14 | /// password 15 | pub password: String, 16 | } 17 | 18 | impl BasicAuthorization for Basic { 19 | fn from_request(req: &Request) -> Result { 20 | if let Some(auth) = req 21 | .headers() 22 | .typed_get::>() 23 | { 24 | return Ok(Basic { 25 | username: auth.username().to_string(), 26 | password: auth.password().to_string(), 27 | }); 28 | } 29 | 30 | Err(AuthorizationError.into()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | >= 1.2.0 | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | 12 | If you discover a vulnerability, please do the following: 13 | 14 | - E-mail your findings to scott_s829 [at] 163 [dot] com. 15 | - Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data. 16 | - Do not reveal the problem to others until it has been resolved. 17 | - Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties. 18 | - Do provide sufficient information to reproduce the problem, so we will be able to resolve it as quickly as possible. Complex vulnerabilities may require further explanation! 19 | -------------------------------------------------------------------------------- /examples/poem/handling-404/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | error::NotFoundError, get, handler, http::StatusCode, listener::TcpListener, web::Path, 3 | EndpointExt, Response, Route, Server, 4 | }; 5 | 6 | #[handler] 7 | fn hello(Path(name): Path) -> String { 8 | format!("hello: {name}") 9 | } 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<(), std::io::Error> { 13 | if std::env::var_os("RUST_LOG").is_none() { 14 | std::env::set_var("RUST_LOG", "poem=debug"); 15 | } 16 | tracing_subscriber::fmt::init(); 17 | 18 | let app = 19 | Route::new() 20 | .at("/hello/:name", get(hello)) 21 | .catch_error(|_: NotFoundError| async move { 22 | Response::builder() 23 | .status(StatusCode::NOT_FOUND) 24 | .body("haha") 25 | }); 26 | 27 | Server::new(TcpListener::bind("0.0.0.0:3000")) 28 | .run(app) 29 | .await 30 | } 31 | -------------------------------------------------------------------------------- /poem/src/web/addr.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{self, Display, Formatter}, 3 | ops::Deref, 4 | }; 5 | 6 | use crate::Addr; 7 | 8 | /// Remote peer's address. 9 | #[derive(Debug, Clone, Default, PartialEq)] 10 | pub struct RemoteAddr(pub Addr); 11 | 12 | impl Deref for RemoteAddr { 13 | type Target = Addr; 14 | 15 | fn deref(&self) -> &Self::Target { 16 | &self.0 17 | } 18 | } 19 | 20 | impl Display for RemoteAddr { 21 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 22 | self.0.fmt(f) 23 | } 24 | } 25 | 26 | /// Local server's address. 27 | #[derive(Debug, Clone, Default, PartialEq)] 28 | pub struct LocalAddr(pub Addr); 29 | 30 | impl Deref for LocalAddr { 31 | type Target = Addr; 32 | 33 | fn deref(&self) -> &Self::Target { 34 | &self.0 35 | } 36 | } 37 | 38 | impl Display for LocalAddr { 39 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 40 | self.0.fmt(f) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /poem-openapi/src/types/external/mod.rs: -------------------------------------------------------------------------------- 1 | mod array; 2 | mod bool; 3 | #[cfg(feature = "bson")] 4 | mod bson; 5 | mod btreemap; 6 | mod btreeset; 7 | #[cfg(feature = "camino")] 8 | mod camino; 9 | mod char; 10 | #[cfg(feature = "chrono")] 11 | mod chrono; 12 | #[cfg(feature = "rust_decimal")] 13 | mod decimal; 14 | mod floats; 15 | #[cfg(feature = "geo")] 16 | mod geo; 17 | mod hashmap; 18 | mod hashset; 19 | #[cfg(feature = "humantime")] 20 | mod humantime; 21 | #[cfg(feature = "humantime")] 22 | mod humantime_wrapper; 23 | mod integers; 24 | mod ip; 25 | mod non_zero; 26 | mod optional; 27 | mod path_buf; 28 | #[cfg(feature = "prost-wkt-types")] 29 | mod prost_wkt_types; 30 | mod regex; 31 | mod slice; 32 | #[cfg(feature = "sqlx")] 33 | mod sqlx; 34 | mod string; 35 | #[cfg(feature = "time")] 36 | mod time; 37 | #[cfg(feature = "ulid")] 38 | mod ulid; 39 | mod unit; 40 | mod uri; 41 | #[cfg(feature = "url")] 42 | mod url; 43 | #[cfg(feature = "uuid")] 44 | mod uuid; 45 | mod vec; 46 | -------------------------------------------------------------------------------- /examples/grpc/middleware/src/middleware.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | endpoint::{BoxEndpoint, EndpointExt}, 3 | Endpoint, Middleware, 4 | }; 5 | 6 | pub(crate) struct ClientMiddleware; 7 | 8 | impl Middleware for ClientMiddleware { 9 | type Output = BoxEndpoint<'static, E::Output>; 10 | 11 | fn transform(&self, ep: E) -> Self::Output { 12 | ep.before(|req| async move { 13 | println!("client request: {}", req.uri().path()); 14 | Ok(req) 15 | }) 16 | .boxed() 17 | } 18 | } 19 | 20 | pub(crate) struct ServerMiddleware; 21 | 22 | impl Middleware for ServerMiddleware { 23 | type Output = BoxEndpoint<'static, E::Output>; 24 | 25 | fn transform(&self, ep: E) -> Self::Output { 26 | ep.before(|req| async move { 27 | println!("handle request: {}", req.original_uri().path()); 28 | Ok(req) 29 | }) 30 | .boxed() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/poem/opentelemetry-jaeger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-opentelemetry" 3 | version.workspace = true 4 | edition.workspace = true 5 | publish.workspace = true 6 | 7 | [dependencies] 8 | poem = { workspace = true, features = ["opentelemetry"] } 9 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 10 | tracing-subscriber.workspace = true 11 | opentelemetry = { version = "0.31.0", features = ["metrics"] } 12 | opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio"] } 13 | opentelemetry-http = { version = "0.31.0" } 14 | opentelemetry-otlp = { version = "0.31.0", default-features = false, features = [ 15 | "trace", 16 | "grpc-tonic", 17 | ] } 18 | reqwest = "0.12" 19 | 20 | [[bin]] 21 | name = "example-opentelemetry-client" 22 | path = "src/client.rs" 23 | 24 | [[bin]] 25 | name = "example-opentelemetry-server1" 26 | path = "src/server1.rs" 27 | 28 | [[bin]] 29 | name = "example-opentelemetry-server2" 30 | path = "src/server2.rs" 31 | -------------------------------------------------------------------------------- /poem-worker/src/context.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use http::StatusCode; 4 | use poem::{FromRequest, Request, RequestBody}; 5 | 6 | #[derive(Clone)] 7 | pub struct Context(Arc); 8 | 9 | impl Context { 10 | pub fn new(ctx: worker::Context) -> Self { 11 | Self(Arc::new(ctx)) 12 | } 13 | 14 | pub fn wait_until(&self, future: F) 15 | where 16 | F: Future + 'static, 17 | { 18 | self.0.wait_until(future); 19 | } 20 | 21 | pub fn pass_through_on_exception(&self) { 22 | self.0.pass_through_on_exception(); 23 | } 24 | } 25 | 26 | impl<'a> FromRequest<'a> for Context { 27 | async fn from_request(req: &'a Request, _body: &mut RequestBody) -> poem::Result { 28 | let ctx = req.data::().ok_or_else(|| { 29 | poem::Error::from_string("failed to get incoming context", StatusCode::BAD_REQUEST) 30 | })?; 31 | 32 | Ok(ctx.clone()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /poem-openapi-derive/src/parameter_style.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::{ToTokens, TokenStreamExt}; 4 | 5 | #[derive(FromMeta)] 6 | pub(crate) enum ParameterStyle { 7 | Label, 8 | Matrix, 9 | Form, 10 | Simple, 11 | SpaceDelimited, 12 | PipeDelimited, 13 | DeepObject, 14 | } 15 | 16 | impl ToTokens for ParameterStyle { 17 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 18 | let name = match self { 19 | ParameterStyle::Label => "Label", 20 | ParameterStyle::Matrix => "Matrix", 21 | ParameterStyle::Form => "Form", 22 | ParameterStyle::Simple => "Simple", 23 | ParameterStyle::SpaceDelimited => "SpaceDelimited", 24 | ParameterStyle::PipeDelimited => "PipeDelimited", 25 | ParameterStyle::DeepObject => "DeepObject", 26 | }; 27 | 28 | tokens.append(Ident::new(name, proc_macro2::Span::call_site())); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/poem/custom-error/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | error::ResponseError, get, handler, http::StatusCode, listener::TcpListener, Result, Route, 3 | Server, 4 | }; 5 | 6 | #[derive(Debug, thiserror::Error)] 7 | #[error("{message}")] 8 | struct CustomError { 9 | message: String, 10 | } 11 | 12 | impl ResponseError for CustomError { 13 | fn status(&self) -> StatusCode { 14 | StatusCode::BAD_REQUEST 15 | } 16 | } 17 | 18 | #[handler] 19 | fn hello() -> Result { 20 | Err(CustomError { 21 | message: "custom error".to_string(), 22 | } 23 | .into()) 24 | } 25 | 26 | #[tokio::main] 27 | async fn main() -> Result<(), std::io::Error> { 28 | if std::env::var_os("RUST_LOG").is_none() { 29 | std::env::set_var("RUST_LOG", "poem=debug"); 30 | } 31 | tracing_subscriber::fmt::init(); 32 | 33 | let app = Route::new().at("/", get(hello)); 34 | Server::new(TcpListener::bind("0.0.0.0:3000")) 35 | .run(app) 36 | .await 37 | } 38 | -------------------------------------------------------------------------------- /poem/src/listener/acme/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types for ACME. 2 | //! 3 | //! Reference: 4 | //! Reference: 5 | 6 | mod auto_cert; 7 | mod builder; 8 | mod client; 9 | mod endpoint; 10 | mod jose; 11 | mod keypair; 12 | mod listener; 13 | mod protocol; 14 | mod resolver; 15 | 16 | pub use auto_cert::AutoCert; 17 | pub use builder::AutoCertBuilder; 18 | pub use client::AcmeClient; 19 | pub use endpoint::{Http01Endpoint, Http01TokensMap}; 20 | pub use listener::{AutoCertAcceptor, AutoCertListener, ResolvedCertListener, issue_cert}; 21 | pub use protocol::ChallengeType; 22 | pub use resolver::{ResolveServerCert, seconds_until_expiry}; 23 | 24 | /// Let's Encrypt production directory url 25 | pub const LETS_ENCRYPT_PRODUCTION: &str = "https://acme-v02.api.letsencrypt.org/directory"; 26 | 27 | /// Let's Encrypt staging directory url 28 | pub const LETS_ENCRYPT_STAGING: &str = "https://acme-staging-v02.api.letsencrypt.org/directory"; 29 | -------------------------------------------------------------------------------- /poem-mcpserver/src/protocol/resources.rs: -------------------------------------------------------------------------------- 1 | //! Resource protocol. 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// A request to list resources. 6 | #[derive(Debug, Deserialize, Default)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct ResourcesListRequest { 9 | /// The cursor to continue listing tools. 10 | pub cursor: Option, 11 | } 12 | 13 | /// Resource information. 14 | #[derive(Debug, Serialize)] 15 | #[serde(rename_all = "camelCase")] 16 | pub struct Resource { 17 | /// The uri of the resource. 18 | pub uri: String, 19 | /// The name of the tool. 20 | pub name: String, 21 | /// The description of the tool. 22 | pub description: String, 23 | /// The mime type of the resource. 24 | pub mime_type: String, 25 | } 26 | 27 | /// A response to a resources/list request. 28 | #[derive(Debug, Serialize)] 29 | #[serde(rename_all = "camelCase")] 30 | pub struct ResourcesListResponse { 31 | /// Resources list. 32 | pub resources: Vec, 33 | } 34 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/unique_items.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashSet, hash::Hash, ops::Deref}; 2 | 3 | use derive_more::Display; 4 | 5 | use crate::{ 6 | registry::MetaSchema, 7 | types::Type, 8 | validation::{Validator, ValidatorMeta}, 9 | }; 10 | 11 | #[derive(Display, Default)] 12 | #[display("uniqueItems()")] 13 | pub struct UniqueItems; 14 | 15 | impl UniqueItems { 16 | #[inline] 17 | pub fn new() -> Self { 18 | Self 19 | } 20 | } 21 | 22 | impl, E: Type + Eq + Hash> Validator for UniqueItems { 23 | #[inline] 24 | fn check(&self, value: &T) -> bool { 25 | let mut set = HashSet::new(); 26 | for item in value.deref() { 27 | if !set.insert(item) { 28 | return false; 29 | } 30 | } 31 | true 32 | } 33 | } 34 | 35 | impl ValidatorMeta for UniqueItems { 36 | fn update_meta(&self, meta: &mut MetaSchema) { 37 | meta.unique_items = Some(true); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | mod max_items; 4 | mod max_length; 5 | mod max_properties; 6 | mod maximum; 7 | mod min_items; 8 | mod min_length; 9 | mod min_properties; 10 | mod minimum; 11 | mod multiple_of; 12 | mod pattern; 13 | mod unique_items; 14 | 15 | pub use max_items::MaxItems; 16 | pub use max_length::MaxLength; 17 | pub use max_properties::MaxProperties; 18 | pub use maximum::Maximum; 19 | pub use min_items::MinItems; 20 | pub use min_length::MinLength; 21 | pub use min_properties::MinProperties; 22 | pub use minimum::Minimum; 23 | pub use multiple_of::MultipleOf; 24 | pub use pattern::Pattern; 25 | pub use unique_items::UniqueItems; 26 | 27 | use crate::registry::MetaSchema; 28 | 29 | /// Represents a validator to validate the input value. 30 | pub trait Validator: Display { 31 | /// Checks if the value is valid. 32 | fn check(&self, value: &T) -> bool; 33 | } 34 | 35 | pub trait ValidatorMeta { 36 | fn update_meta(&self, meta: &mut MetaSchema); 37 | } 38 | -------------------------------------------------------------------------------- /examples/poem/acme-alpn-01/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, 3 | listener::{ 4 | acme::{AutoCert, LETS_ENCRYPT_PRODUCTION}, 5 | Listener, TcpListener, 6 | }, 7 | middleware::Tracing, 8 | web::Path, 9 | EndpointExt, Route, Server, 10 | }; 11 | 12 | #[handler] 13 | fn hello(Path(name): Path) -> String { 14 | format!("hello: {name}") 15 | } 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<(), std::io::Error> { 19 | if std::env::var_os("RUST_LOG").is_none() { 20 | std::env::set_var("RUST_LOG", "poem=debug"); 21 | } 22 | tracing_subscriber::fmt::init(); 23 | 24 | let auto_cert = AutoCert::builder() 25 | .directory_url(LETS_ENCRYPT_PRODUCTION) 26 | .domain("poem.rs") 27 | .build()?; 28 | 29 | let app = Route::new().at("/hello/:name", get(hello)).with(Tracing); 30 | 31 | Server::new(TcpListener::bind("0.0.0.0:443").acme(auto_cert)) 32 | .name("hello-world") 33 | .run(app) 34 | .await 35 | } 36 | -------------------------------------------------------------------------------- /poem/src/route/mod.rs: -------------------------------------------------------------------------------- 1 | //! Route object and DSL 2 | 3 | mod internal; 4 | mod router; 5 | mod router_domain; 6 | mod router_method; 7 | mod router_scheme; 8 | 9 | pub(crate) use internal::radix_tree::PathParams; 10 | pub use router::{PathPattern, Route}; 11 | #[allow(unreachable_pub)] 12 | pub use router_domain::RouteDomain; 13 | #[allow(unreachable_pub)] 14 | pub use router_method::{ 15 | RouteMethod, connect, delete, get, head, options, patch, post, put, trace, 16 | }; 17 | #[allow(unreachable_pub)] 18 | pub use router_scheme::RouteScheme; 19 | 20 | use crate::error::RouteError; 21 | 22 | pub(crate) fn check_result(res: Result) -> T { 23 | match res { 24 | Ok(value) => value, 25 | Err(RouteError::InvalidPath(path)) => panic!("invalid path: {path}"), 26 | Err(RouteError::Duplicate(path)) => panic!("duplicate path: {path}"), 27 | Err(RouteError::InvalidRegex { path, regex }) => { 28 | panic!("invalid regex in path: {path} `{regex}`") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /poem-mcpserver/src/protocol/content.rs: -------------------------------------------------------------------------------- 1 | //! Content type. 2 | 3 | use serde::Serialize; 4 | use serde_json::Value; 5 | 6 | /// A content that can be sent to the client. 7 | #[derive(Debug, Serialize)] 8 | #[serde(rename_all = "camelCase", tag = "type")] 9 | pub enum Content { 10 | /// A text content. 11 | Text { 12 | /// The text content. 13 | text: String, 14 | }, 15 | /// An image content. 16 | Image { 17 | /// The image data. 18 | data: String, 19 | /// The MIME type of the image. 20 | mime_type: String, 21 | }, 22 | /// A link to a resource. 23 | ResourceLink { 24 | /// The URI of the resource. 25 | uri: String, 26 | /// The name of the resource. 27 | name: String, 28 | /// The description of the resource. 29 | description: String, 30 | /// The MIME type of the resource. 31 | mime_type: String, 32 | /// Additional annotations for the resource. 33 | annotations: Value, 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /examples/mcpserver/counter/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem_mcpserver::{content::Text, stdio::stdio, McpServer, Tools}; 2 | 3 | struct Counter { 4 | count: i32, 5 | } 6 | 7 | /// This server provides a counter tool that can increment and decrement values. 8 | /// 9 | /// The counter starts at 0 and can be modified using the 'increment' and 10 | /// 'decrement' tools. Use 'get_value' to check the current count. 11 | #[Tools] 12 | impl Counter { 13 | /// Increment the counter by 1 14 | async fn increment(&mut self) -> Text { 15 | self.count += 1; 16 | Text(self.count) 17 | } 18 | 19 | /// Decrement the counter by 1 20 | async fn decrement(&mut self) -> Text { 21 | self.count -= 1; 22 | Text(self.count) 23 | } 24 | 25 | /// Get the current counter value 26 | async fn get_value(&self) -> Text { 27 | Text(self.count) 28 | } 29 | } 30 | 31 | #[tokio::main] 32 | async fn main() -> std::io::Result<()> { 33 | stdio(McpServer::new().tools(Counter { count: 0 })).await 34 | } 35 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/maximum.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Display; 2 | use num_traits::AsPrimitive; 3 | 4 | use crate::{ 5 | registry::MetaSchema, 6 | validation::{Validator, ValidatorMeta}, 7 | }; 8 | 9 | #[derive(Display)] 10 | #[display("maximum({n}, exclusive: {exclusive})")] 11 | pub struct Maximum { 12 | n: f64, 13 | exclusive: bool, 14 | } 15 | 16 | impl Maximum { 17 | #[inline] 18 | pub fn new(n: f64, exclusive: bool) -> Self { 19 | Self { n, exclusive } 20 | } 21 | } 22 | 23 | impl> Validator for Maximum { 24 | #[inline] 25 | fn check(&self, value: &T) -> bool { 26 | if self.exclusive { 27 | value.as_() < self.n 28 | } else { 29 | value.as_() <= self.n 30 | } 31 | } 32 | } 33 | 34 | impl ValidatorMeta for Maximum { 35 | fn update_meta(&self, meta: &mut MetaSchema) { 36 | meta.maximum = Some(self.n); 37 | if self.exclusive { 38 | meta.exclusive_maximum = Some(true); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/minimum.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Display; 2 | use num_traits::AsPrimitive; 3 | 4 | use crate::{ 5 | registry::MetaSchema, 6 | validation::{Validator, ValidatorMeta}, 7 | }; 8 | 9 | #[derive(Display)] 10 | #[display("minimum({n}, exclusive: {exclusive})")] 11 | pub struct Minimum { 12 | n: f64, 13 | exclusive: bool, 14 | } 15 | 16 | impl Minimum { 17 | #[inline] 18 | pub fn new(n: f64, exclusive: bool) -> Self { 19 | Self { n, exclusive } 20 | } 21 | } 22 | 23 | impl> Validator for Minimum { 24 | #[inline] 25 | fn check(&self, value: &T) -> bool { 26 | if self.exclusive { 27 | value.as_() > self.n 28 | } else { 29 | value.as_() >= self.n 30 | } 31 | } 32 | } 33 | 34 | impl ValidatorMeta for Minimum { 35 | fn update_meta(&self, meta: &mut MetaSchema) { 36 | meta.minimum = Some(self.n); 37 | if self.exclusive { 38 | meta.exclusive_minimum = Some(true); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/max_properties.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, HashMap}; 2 | 3 | use derive_more::Display; 4 | 5 | use crate::{ 6 | registry::MetaSchema, 7 | validation::{Validator, ValidatorMeta}, 8 | }; 9 | 10 | #[derive(Display)] 11 | #[display("maxProperties({len})")] 12 | pub struct MaxProperties { 13 | len: usize, 14 | } 15 | 16 | impl MaxProperties { 17 | #[inline] 18 | pub fn new(len: usize) -> Self { 19 | Self { len } 20 | } 21 | } 22 | 23 | impl Validator> for MaxProperties { 24 | #[inline] 25 | fn check(&self, value: &HashMap) -> bool { 26 | value.len() <= self.len 27 | } 28 | } 29 | 30 | impl Validator> for MaxProperties { 31 | #[inline] 32 | fn check(&self, value: &BTreeMap) -> bool { 33 | value.len() <= self.len 34 | } 35 | } 36 | 37 | impl ValidatorMeta for MaxProperties { 38 | fn update_meta(&self, meta: &mut MetaSchema) { 39 | meta.max_properties = Some(self.len); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /poem-openapi/src/validation/min_properties.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, HashMap}; 2 | 3 | use derive_more::Display; 4 | 5 | use crate::{ 6 | registry::MetaSchema, 7 | validation::{Validator, ValidatorMeta}, 8 | }; 9 | 10 | #[derive(Display)] 11 | #[display("minProperties({len})")] 12 | pub struct MinProperties { 13 | len: usize, 14 | } 15 | 16 | impl MinProperties { 17 | #[inline] 18 | pub fn new(len: usize) -> Self { 19 | Self { len } 20 | } 21 | } 22 | 23 | impl Validator> for MinProperties { 24 | #[inline] 25 | fn check(&self, value: &HashMap) -> bool { 26 | value.len() >= self.len 27 | } 28 | } 29 | 30 | impl Validator> for MinProperties { 31 | #[inline] 32 | fn check(&self, value: &BTreeMap) -> bool { 33 | value.len() >= self.len 34 | } 35 | } 36 | 37 | impl ValidatorMeta for MinProperties { 38 | fn update_meta(&self, meta: &mut MetaSchema) { 39 | meta.min_properties = Some(self.len); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/openapi/poem-extractor/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, web::Data, EndpointExt, Route, Server}; 2 | use poem_openapi::{payload::PlainText, OpenApi, OpenApiService}; 3 | 4 | struct Api; 5 | 6 | #[OpenApi] 7 | impl Api { 8 | #[oai(path = "/hello", method = "get")] 9 | async fn index(&self, data: Data<&i32>) -> PlainText { 10 | PlainText(format!("{}", data.0)) 11 | } 12 | } 13 | 14 | #[tokio::main] 15 | async fn main() -> Result<(), std::io::Error> { 16 | if std::env::var_os("RUST_LOG").is_none() { 17 | std::env::set_var("RUST_LOG", "poem=debug"); 18 | } 19 | tracing_subscriber::fmt::init(); 20 | 21 | let api_service = 22 | OpenApiService::new(Api, "Poem Extractor", "1.0").server("http://localhost:3000/api"); 23 | let ui = api_service.swagger_ui(); 24 | 25 | Server::new(TcpListener::bind("0.0.0.0:3000")) 26 | .run( 27 | Route::new() 28 | .nest("/api", api_service.data(100i32)) 29 | .nest("/", ui), 30 | ) 31 | .await 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/code-coverage.yml: -------------------------------------------------------------------------------- 1 | name: Code Coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | cover: 10 | runs-on: ubuntu-latest 11 | container: 12 | image: xd009642/tarpaulin:develop-nightly 13 | options: --security-opt seccomp=unconfined 14 | services: 15 | redis: 16 | image: redis:5.0.7 17 | ports: 18 | - 6379:6379 19 | options: --entrypoint redis-server 20 | steps: 21 | - uses: actions/checkout@v5 22 | - name: Install Protoc 23 | uses: arduino/setup-protoc@v1 24 | - name: Install Redis 25 | run: | 26 | apt-get update 27 | apt-get install -y redis-server 28 | redis-server --daemonize yes 29 | redis-cli ping 30 | - name: Generate code coverage 31 | run: | 32 | cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out xml 33 | - name: Upload To codecov.io 34 | uses: codecov/codecov-action@v3 35 | with: 36 | token: ${{secrets.CODECOV_TOKEN}} 37 | -------------------------------------------------------------------------------- /examples/openapi/hello-world/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, Route, Server}; 2 | use poem_openapi::{param::Query, payload::PlainText, OpenApi, OpenApiService}; 3 | 4 | struct Api; 5 | 6 | #[OpenApi] 7 | impl Api { 8 | #[oai(path = "/hello", method = "get")] 9 | async fn index(&self, name: Query>) -> PlainText { 10 | match name.0 { 11 | Some(name) => PlainText(format!("hello, {name}!")), 12 | None => PlainText("hello!".to_string()), 13 | } 14 | } 15 | } 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<(), std::io::Error> { 19 | if std::env::var_os("RUST_LOG").is_none() { 20 | std::env::set_var("RUST_LOG", "poem=debug"); 21 | } 22 | tracing_subscriber::fmt::init(); 23 | 24 | let api_service = 25 | OpenApiService::new(Api, "Hello World", "1.0").server("http://localhost:3000/api"); 26 | let ui = api_service.swagger_ui(); 27 | 28 | Server::new(TcpListener::bind("0.0.0.0:3000")) 29 | .run(Route::new().nest("/api", api_service).nest("/", ui)) 30 | .await 31 | } 32 | -------------------------------------------------------------------------------- /examples/poem/requestid/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, 3 | listener::TcpListener, 4 | middleware::{ReqId, RequestId, ReuseId, Tracing}, 5 | EndpointExt, Route, 6 | }; 7 | 8 | #[handler] 9 | fn show_request_id(id: ReqId) -> ReqId { 10 | id 11 | } 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<(), std::io::Error> { 15 | if std::env::var_os("RUST_LOG").is_none() { 16 | std::env::set_var("RUST_LOG", "poem=debug"); 17 | } 18 | tracing_subscriber::fmt::init(); 19 | 20 | let app = Route::new() 21 | .at("/", get(show_request_id)) 22 | .with(Tracing) 23 | // `RequestId` must be applied _after_ tracing, for the ID to be logged in the trace span 24 | .with(RequestId::default().reuse_id(ReuseId::Use)); 25 | 26 | println!("example server listening on 127.0.0.1:8080"); 27 | println!("try `curl -v http://127.0.0.1:8080/`"); 28 | println!("try `curl -v -H 'x-request-id: 12345' http://127.0.0.1:8080/`"); 29 | poem::Server::new(TcpListener::bind("127.0.0.1:8080")) 30 | .run(app) 31 | .await 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/grpc/jsoncodec/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, middleware::Tracing, EndpointExt, Server}; 2 | use poem_grpc::{Request, Response, RouteGrpc, Status}; 3 | 4 | poem_grpc::include_proto!("helloworld"); 5 | 6 | struct GreeterService; 7 | 8 | impl Greeter for GreeterService { 9 | async fn say_hello( 10 | &self, 11 | request: Request, 12 | ) -> Result, Status> { 13 | let reply = HelloReply { 14 | message: format!("Hello {}!", request.into_inner().name), 15 | }; 16 | Ok(Response::new(reply)) 17 | } 18 | } 19 | 20 | #[tokio::main] 21 | async fn main() -> Result<(), std::io::Error> { 22 | if std::env::var_os("RUST_LOG").is_none() { 23 | std::env::set_var("RUST_LOG", "poem=debug"); 24 | } 25 | tracing_subscriber::fmt::init(); 26 | 27 | Server::new(TcpListener::bind("0.0.0.0:3000")) 28 | .run( 29 | RouteGrpc::new() 30 | .add_service(GreeterServer::new(GreeterService)) 31 | .with(Tracing), 32 | ) 33 | .await 34 | } 35 | -------------------------------------------------------------------------------- /poem/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /poem-derive/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /poem-grpc/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /poem-lambda/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /poem-openapi/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /poem-grpc-build/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /poem-openapi-derive/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/openapi/custom-payload/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, Route, Server}; 2 | use poem_openapi::{Object, OpenApi, OpenApiService}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::bcs_payload::Bcs; 6 | 7 | mod bcs_payload; 8 | 9 | #[derive(Debug, Deserialize, Object, Serialize)] 10 | struct MyStruct { 11 | first_name: String, 12 | last_name: String, 13 | } 14 | 15 | struct Api; 16 | 17 | #[OpenApi] 18 | impl Api { 19 | #[oai(path = "/echo", method = "post")] 20 | async fn index(&self, input: Bcs) -> Bcs { 21 | input 22 | } 23 | } 24 | 25 | #[tokio::main] 26 | async fn main() -> Result<(), std::io::Error> { 27 | if std::env::var_os("RUST_LOG").is_none() { 28 | std::env::set_var("RUST_LOG", "poem=debug"); 29 | } 30 | 31 | let api_service = 32 | OpenApiService::new(Api, "Hello World", "1.0").server("http://localhost:3000/api"); 33 | let ui = api_service.swagger_ui(); 34 | 35 | Server::new(TcpListener::bind("0.0.0.0:3000")) 36 | .run(Route::new().nest("/api", api_service).nest("/", ui)) 37 | .await 38 | } 39 | -------------------------------------------------------------------------------- /examples/poem/tokio-metrics/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use poem::{ 4 | get, handler, 5 | listener::TcpListener, 6 | middleware::{TokioMetrics, Tracing}, 7 | EndpointExt, Route, Server, 8 | }; 9 | 10 | #[handler] 11 | async fn a() -> &'static str { 12 | "a" 13 | } 14 | 15 | #[handler] 16 | async fn b() -> &'static str { 17 | tokio::time::sleep(Duration::from_millis(10)).await; 18 | "b" 19 | } 20 | 21 | #[tokio::main] 22 | async fn main() -> Result<(), std::io::Error> { 23 | if std::env::var_os("RUST_LOG").is_none() { 24 | std::env::set_var("RUST_LOG", "poem=debug"); 25 | } 26 | tracing_subscriber::fmt::init(); 27 | 28 | let metrics_a = TokioMetrics::new(); 29 | let metrics_b = TokioMetrics::new(); 30 | 31 | let app = Route::new() 32 | .at("/metrics/a", metrics_a.exporter()) 33 | .at("/metrics/b", metrics_b.exporter()) 34 | .at("/a", get(a).with(metrics_a)) 35 | .at("/b", get(b).with(metrics_b)) 36 | .with(Tracing); 37 | Server::new(TcpListener::bind("0.0.0.0:3000")) 38 | .run(app) 39 | .await 40 | } 41 | -------------------------------------------------------------------------------- /poem-openapi/src/ui/scalar/mod.rs: -------------------------------------------------------------------------------- 1 | use poem::{Endpoint, endpoint::make_sync, web::Html}; 2 | 3 | const SCALAR_JS: &str = include_str!("scalar.min.js"); 4 | 5 | const SCALAR_TEMPLATE: &str = r#" 6 | 7 | 8 | 9 | Scalar 10 | 11 | 14 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | "#; 32 | 33 | pub(crate) fn create_html(document: &str) -> String { 34 | SCALAR_TEMPLATE 35 | .replace("{:script}", SCALAR_JS) 36 | .replace("{:spec}", document) 37 | } 38 | 39 | pub(crate) fn create_endpoint(document: String) -> impl Endpoint + 'static { 40 | let ui_html = create_html(&document); 41 | poem::Route::new().at("/", make_sync(move |_| Html(ui_html.clone()))) 42 | } 43 | -------------------------------------------------------------------------------- /examples/openapi/poem-middleware/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, middleware::SetHeader, Endpoint, EndpointExt, Route}; 2 | use poem_openapi::{payload::PlainText, OpenApi, OpenApiService}; 3 | 4 | struct Api; 5 | 6 | #[OpenApi] 7 | impl Api { 8 | #[oai(path = "/hello", method = "get", transform = "set_header")] 9 | async fn index(&self) -> PlainText<&'static str> { 10 | PlainText("hello!") 11 | } 12 | } 13 | 14 | fn set_header(ep: impl Endpoint) -> impl Endpoint { 15 | ep.with(SetHeader::new().appending("Custom-Header", "test")) 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> Result<(), std::io::Error> { 20 | if std::env::var_os("RUST_LOG").is_none() { 21 | std::env::set_var("RUST_LOG", "poem=debug"); 22 | } 23 | tracing_subscriber::fmt::init(); 24 | 25 | let api_service = 26 | OpenApiService::new(Api, "Poem Middleware", "1.0").server("http://localhost:3000/api"); 27 | let ui = api_service.swagger_ui(); 28 | 29 | poem::Server::new(TcpListener::bind("0.0.0.0:3000")) 30 | .run(Route::new().nest("/api", api_service).nest("/", ui)) 31 | .await 32 | } 33 | -------------------------------------------------------------------------------- /examples/poem/redis-session/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, 3 | listener::TcpListener, 4 | session::{CookieConfig, RedisStorage, ServerSession, Session}, 5 | EndpointExt, Route, Server, 6 | }; 7 | use redis::{aio::ConnectionManager, Client}; 8 | 9 | #[handler] 10 | async fn count(session: &Session) -> String { 11 | let count = session.get::("count").unwrap_or(0) + 1; 12 | session.set("count", count); 13 | format!("Hello!\nHow many times have seen you: {count}") 14 | } 15 | 16 | #[tokio::main] 17 | async fn main() -> Result<(), std::io::Error> { 18 | if std::env::var_os("RUST_LOG").is_none() { 19 | std::env::set_var("RUST_LOG", "poem=debug"); 20 | } 21 | tracing_subscriber::fmt::init(); 22 | 23 | let client = Client::open("redis://127.0.0.1/").unwrap(); 24 | 25 | let app = Route::new().at("/", get(count)).with(ServerSession::new( 26 | CookieConfig::default().secure(false), 27 | RedisStorage::new(ConnectionManager::new(client).await.unwrap()), 28 | )); 29 | Server::new(TcpListener::bind("0.0.0.0:3000")) 30 | .run(app) 31 | .await 32 | } 33 | -------------------------------------------------------------------------------- /poem-lambda/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | # [5.1.3] 2025-06-06 8 | 9 | - chore(deps): update lambda_http requirement from 0.13.0 to 0.15.0 [#1045](https://github.com/poem-web/poem/pull/1045) 10 | 11 | # [5.1.2] 2025-03-24 12 | 13 | - Update MSRV to `1.85.0` 14 | 15 | # [5.1.1] 2024-11-20 16 | 17 | - Update MSRV to `1.81.0` 18 | 19 | # [5.0.0] 2024-03-30 20 | 21 | - use AFIT instead of `async_trait` 22 | - Bump `lambda_http` from `0.9` to `0.10` 23 | 24 | # [1.3.47] 2022-10-19 25 | 26 | - Bump lambda_http from `0.6.0` to `0.7.0` 27 | 28 | # [1.3.41] 29 | 30 | - Upgrade lambda_http version to `v0.6.0` 31 | 32 | # [1.3.16] 2022-3-18 33 | 34 | - Bump `lambda_http` from `0.4.1` to `0.5.1`. 35 | 36 | # [1.0.19] 2021-11-03 37 | 38 | # [1.0.19] 2021-11-03 39 | 40 | - Use Rust 2021 edition. 41 | 42 | # [1.0.12] 2021-10-27 43 | 44 | - Return the correct payload type to the gateway. 45 | 46 | -------------------------------------------------------------------------------- /examples/poem/mongodb/README.md: -------------------------------------------------------------------------------- 1 | # MongoDB Example 2 | Basic integration [MongoDB](https://www.mongodb.com/) in poem 3 | 4 | ## Usage 5 | 6 | ### Available Routes 7 | 8 | #### `GET /user` 9 | 10 | Query users from MongoDB. 11 | ```json 12 | [ 13 | { 14 | "_id": { 15 | "$oid": "614f73c715681cf389fa93f9" 16 | }, 17 | "name": "JungWoo", 18 | "email": "jungwoo@cineuse.com", 19 | "age": 19 20 | }, 21 | { 22 | "_id": { 23 | "$oid": "614f75e67ade426aa3f73b1c" 24 | }, 25 | "name": "Jack", 26 | "email": "jack@gmail.com", 27 | "age": 19 28 | } 29 | ] 30 | ``` 31 | 32 | #### `POST /user` 33 | 34 | Inserts a new user into the MongoDB. 35 | 36 | Provide a JSON payload with a name. Eg: 37 | ```json 38 | { 39 | "name": "JungWoo", 40 | "email": "jungwoo@cineuse.com", 41 | "age": 19 42 | } 43 | ``` 44 | 45 | On success, a response like the following is returned: 46 | ```json 47 | { 48 | "_id": { 49 | "$oid": "614f73c715681cf389fa93f9" 50 | }, 51 | "name": "JungWoo", 52 | "email": "jungwoo@cineuse.com", 53 | "age": 19 54 | } 55 | ``` -------------------------------------------------------------------------------- /examples/poem/middleware_fn/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, listener::TcpListener, Endpoint, EndpointExt, IntoResponse, Request, Response, 3 | Result, Route, Server, 4 | }; 5 | 6 | #[handler] 7 | fn index() -> String { 8 | "hello".to_string() 9 | } 10 | 11 | async fn log(next: E, req: Request) -> Result { 12 | println!("request: {}", req.uri().path()); 13 | let res = next.call(req).await; 14 | 15 | match res { 16 | Ok(resp) => { 17 | let resp = resp.into_response(); 18 | println!("response: {}", resp.status()); 19 | Ok(resp) 20 | } 21 | Err(err) => { 22 | println!("error: {err}"); 23 | Err(err) 24 | } 25 | } 26 | } 27 | 28 | #[tokio::main] 29 | async fn main() -> Result<(), std::io::Error> { 30 | if std::env::var_os("RUST_LOG").is_none() { 31 | std::env::set_var("RUST_LOG", "poem=debug"); 32 | } 33 | tracing_subscriber::fmt::init(); 34 | 35 | let app = Route::new().at("/", get(index)).around(log); 36 | Server::new(TcpListener::bind("0.0.0.0:3000")) 37 | .run(app) 38 | .await 39 | } 40 | -------------------------------------------------------------------------------- /poem/src/endpoint/catch_all_error.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, marker::PhantomData}; 2 | 3 | use crate::{Endpoint, Error, IntoResponse, Request, Response, Result}; 4 | 5 | /// Endpoint for the [`catch_all_error`](super::EndpointExt::catch_all_error) 6 | /// method. 7 | pub struct CatchAllError { 8 | inner: E, 9 | f: F, 10 | _mark: PhantomData, 11 | } 12 | 13 | impl CatchAllError { 14 | #[inline] 15 | pub(crate) fn new(inner: E, f: F) -> CatchAllError { 16 | Self { 17 | inner, 18 | f, 19 | _mark: PhantomData, 20 | } 21 | } 22 | } 23 | 24 | impl Endpoint for CatchAllError 25 | where 26 | E: Endpoint, 27 | F: Fn(Error) -> Fut + Send + Sync, 28 | Fut: Future + Send, 29 | R: IntoResponse + Send + Sync, 30 | { 31 | type Output = Response; 32 | 33 | async fn call(&self, req: Request) -> Result { 34 | match self.inner.call(req).await { 35 | Ok(resp) => Ok(resp.into_response()), 36 | Err(err) => Ok((self.f)(err).await.into_response()), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/poem/tera-templating/src/main.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use poem::{ 3 | error::InternalServerError, 4 | get, handler, 5 | listener::TcpListener, 6 | web::{Html, Path}, 7 | Route, Server, 8 | }; 9 | use tera::{Context, Tera}; 10 | 11 | static TEMPLATES: Lazy = Lazy::new(|| { 12 | let mut tera = match Tera::new("templates/**/*") { 13 | Ok(t) => t, 14 | Err(e) => { 15 | println!("Parsing error(s): {e}"); 16 | ::std::process::exit(1); 17 | } 18 | }; 19 | tera.autoescape_on(vec![".html", ".sql"]); 20 | tera 21 | }); 22 | 23 | #[handler] 24 | fn hello(Path(name): Path) -> Result, poem::Error> { 25 | let mut context = Context::new(); 26 | context.insert("name", &name); 27 | TEMPLATES 28 | .render("index.html.tera", &context) 29 | .map_err(InternalServerError) 30 | .map(Html) 31 | } 32 | 33 | #[tokio::main] 34 | async fn main() -> Result<(), std::io::Error> { 35 | let app = Route::new().at("/hello/:name", get(hello)); 36 | Server::new(TcpListener::bind("0.0.0.0:3000")) 37 | .run(app) 38 | .await 39 | } 40 | -------------------------------------------------------------------------------- /examples/poem/custom-extractor/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, http::StatusCode, listener::TcpListener, Error, FromRequest, Request, 3 | RequestBody, Result, Route, Server, 4 | }; 5 | 6 | struct Token(String); 7 | 8 | // Implements a token extractor 9 | impl<'a> FromRequest<'a> for Token { 10 | async fn from_request(req: &'a Request, _body: &mut RequestBody) -> Result { 11 | let token = req 12 | .headers() 13 | .get("MyToken") 14 | .and_then(|value| value.to_str().ok()) 15 | .ok_or_else(|| Error::from_string("missing token", StatusCode::BAD_REQUEST))?; 16 | Ok(Token(token.to_string())) 17 | } 18 | } 19 | 20 | #[handler] 21 | async fn index(token: Token) { 22 | assert_eq!(token.0, "token123"); 23 | } 24 | 25 | #[tokio::main] 26 | async fn main() -> Result<(), std::io::Error> { 27 | if std::env::var_os("RUST_LOG").is_none() { 28 | std::env::set_var("RUST_LOG", "poem=debug"); 29 | } 30 | tracing_subscriber::fmt::init(); 31 | 32 | let app = Route::new().at("/", get(index)); 33 | Server::new(TcpListener::bind("0.0.0.0:3000")) 34 | .run(app) 35 | .await 36 | } 37 | -------------------------------------------------------------------------------- /poem-mcpserver-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod tools; 2 | mod utils; 3 | 4 | use darling::FromMeta; 5 | use proc_macro::TokenStream; 6 | use syn::{ItemImpl, parse_macro_input}; 7 | 8 | macro_rules! parse_nested_meta { 9 | ($ty:ty, $args:expr) => {{ 10 | let meta = match darling::ast::NestedMeta::parse_meta_list(proc_macro2::TokenStream::from( 11 | $args, 12 | )) { 13 | Ok(v) => v, 14 | Err(e) => { 15 | return TokenStream::from(darling::Error::from(e).write_errors()); 16 | } 17 | }; 18 | 19 | match <$ty>::from_list(&meta) { 20 | Ok(object_args) => object_args, 21 | Err(err) => return TokenStream::from(err.write_errors()), 22 | } 23 | }}; 24 | } 25 | 26 | #[proc_macro_attribute] 27 | #[allow(non_snake_case)] 28 | pub fn Tools(args: TokenStream, input: TokenStream) -> TokenStream { 29 | let tool_args = parse_nested_meta!(tools::ToolsArgs, args); 30 | let item_impl = parse_macro_input!(input as ItemImpl); 31 | match tools::generate(tool_args, item_impl) { 32 | Ok(stream) => stream.into(), 33 | Err(err) => err.write_errors().into(), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/grpc/helloworld_compressed/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, Server}; 2 | use poem_grpc::{CompressionEncoding, Request, Response, RouteGrpc, Status}; 3 | 4 | poem_grpc::include_proto!("helloworld"); 5 | 6 | struct GreeterService; 7 | 8 | impl Greeter for GreeterService { 9 | async fn say_hello( 10 | &self, 11 | request: Request, 12 | ) -> Result, Status> { 13 | let reply = HelloReply { 14 | message: format!("Hello {}!", request.into_inner().name), 15 | }; 16 | Ok(Response::new(reply)) 17 | } 18 | } 19 | 20 | #[tokio::main] 21 | async fn main() -> Result<(), std::io::Error> { 22 | let route = RouteGrpc::new().add_service( 23 | GreeterServer::new(GreeterService) 24 | .send_compressed(CompressionEncoding::GZIP) 25 | .accept_compressed([ 26 | CompressionEncoding::GZIP, 27 | CompressionEncoding::DEFLATE, 28 | CompressionEncoding::BROTLI, 29 | CompressionEncoding::ZSTD, 30 | ]), 31 | ); 32 | Server::new(TcpListener::bind("0.0.0.0:3000")) 33 | .run(route) 34 | .await 35 | } 36 | -------------------------------------------------------------------------------- /poem/src/endpoint/inspect_err.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{Endpoint, Request, Result}; 4 | 5 | /// Endpoint for the 6 | /// [`inspect_err`](super::EndpointExt::inspect_err) method. 7 | pub struct InspectError { 8 | inner: E, 9 | f: F, 10 | _mark: PhantomData, 11 | } 12 | 13 | impl InspectError { 14 | #[inline] 15 | pub(crate) fn new(inner: E, f: F) -> InspectError { 16 | Self { 17 | inner, 18 | f, 19 | _mark: PhantomData, 20 | } 21 | } 22 | } 23 | 24 | impl Endpoint for InspectError 25 | where 26 | E: Endpoint, 27 | F: Fn(&ErrType) + Send + Sync, 28 | ErrType: std::error::Error + Send + Sync + 'static, 29 | { 30 | type Output = E::Output; 31 | 32 | async fn call(&self, req: Request) -> Result { 33 | match self.inner.call(req).await { 34 | Ok(resp) => Ok(resp), 35 | Err(err) if err.is::() => { 36 | (self.f)(err.downcast_ref::().unwrap()); 37 | Err(err) 38 | } 39 | Err(err) => Err(err), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /poem-grpc/src/service.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use crate::{Request, Response, status::Status, streaming::Streaming}; 4 | 5 | /// Represent a GRPC service 6 | pub trait Service { 7 | /// The name of the GRPC service 8 | const NAME: &'static str; 9 | } 10 | 11 | pub trait UnaryService { 12 | type Response; 13 | 14 | fn call( 15 | &self, 16 | request: Request, 17 | ) -> impl Future, Status>> + Send; 18 | } 19 | 20 | pub trait ClientStreamingService { 21 | type Response; 22 | 23 | fn call( 24 | &self, 25 | request: Request>, 26 | ) -> impl Future, Status>> + Send; 27 | } 28 | 29 | pub trait ServerStreamingService { 30 | type Response; 31 | 32 | fn call( 33 | &self, 34 | request: Request, 35 | ) -> impl Future>, Status>> + Send; 36 | } 37 | 38 | pub trait BidirectionalStreamingService { 39 | type Response; 40 | 41 | fn call( 42 | &self, 43 | request: Request>, 44 | ) -> impl Future>, Status>> + Send; 45 | } 46 | -------------------------------------------------------------------------------- /poem-openapi/src/docs/newtype.md: -------------------------------------------------------------------------------- 1 | Define a new type. 2 | 3 | # Macro parameters 4 | 5 | | Attribute | Description | Type | Optional | 6 | |----------------|--------------------------------------------------------------|--------|----------| 7 | | from_json | Implement `ParseFromJSON` trait. Default is `true` | bool | Y | 8 | | from_parameter | Implement `ParseFromParameter` trait. Default is `true` | bool | Y | 9 | | from_multipart | Implement `ParseFromMultipartField` trait. Default is `true` | bool | Y | 10 | | to_json | Implement `ToJSON` trait. Default is `true` | bool | Y | 11 | | to_header | Implement `ToHeader` trait. Default is `true` | bool | Y | 12 | | external_docs | Specify a external resource for extended documentation | string | Y | 13 | | example | Indicates that the type has implemented `Example` trait | bool | Y | 14 | | rename | Rename the type | string | Y | 15 | 16 | # Examples 17 | 18 | ```rust 19 | use poem_openapi::NewType; 20 | 21 | #[derive(NewType)] 22 | struct MyString(String); 23 | ``` 24 | -------------------------------------------------------------------------------- /poem-openapi/src/docs/oauth_scopes.md: -------------------------------------------------------------------------------- 1 | Define a OAuth scopes. 2 | 3 | # Macro parameters 4 | 5 | | Attribute | Description | Type | Optional | 6 | |------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| 7 | | rename_all | Rename all the items according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE". | string | Y | 8 | 9 | # Item parameters 10 | 11 | | Attribute | Description | Type | Optional | 12 | |-----------|-----------------------|--------|----------| 13 | | rename | Rename the scope name | string | Y | 14 | 15 | # Examples 16 | 17 | ```rust 18 | use poem_openapi::OAuthScopes; 19 | 20 | #[derive(OAuthScopes)] 21 | enum GithubScopes { 22 | /// Read data 23 | Read, 24 | /// Write data 25 | Write, 26 | } 27 | ``` -------------------------------------------------------------------------------- /examples/grpc/helloworld/proto/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | 21 | package helloworld; 22 | 23 | // The greeting service definition. 24 | service Greeter { 25 | // Sends a greeting 26 | rpc SayHello (HelloRequest) returns (HelloReply) {} 27 | } 28 | 29 | // The request message containing the user's name. 30 | message HelloRequest { 31 | string name = 1; 32 | } 33 | 34 | // The response message containing the greetings 35 | message HelloReply { 36 | string message = 1; 37 | } -------------------------------------------------------------------------------- /examples/grpc/jsoncodec/proto/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | 21 | package helloworld; 22 | 23 | // The greeting service definition. 24 | service Greeter { 25 | // Sends a greeting 26 | rpc SayHello (HelloRequest) returns (HelloReply) {} 27 | } 28 | 29 | // The request message containing the user's name. 30 | message HelloRequest { 31 | string name = 1; 32 | } 33 | 34 | // The response message containing the greetings 35 | message HelloReply { 36 | string message = 1; 37 | } -------------------------------------------------------------------------------- /examples/grpc/middleware/proto/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | 21 | package helloworld; 22 | 23 | // The greeting service definition. 24 | service Greeter { 25 | // Sends a greeting 26 | rpc SayHello (HelloRequest) returns (HelloReply) {} 27 | } 28 | 29 | // The request message containing the user's name. 30 | message HelloRequest { 31 | string name = 1; 32 | } 33 | 34 | // The response message containing the greetings 35 | message HelloReply { 36 | string message = 1; 37 | } -------------------------------------------------------------------------------- /examples/grpc/reflection/proto/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | 21 | package helloworld; 22 | 23 | // The greeting service definition. 24 | service Greeter { 25 | // Sends a greeting 26 | rpc SayHello (HelloRequest) returns (HelloReply) {} 27 | } 28 | 29 | // The request message containing the user's name. 30 | message HelloRequest { 31 | string name = 1; 32 | } 33 | 34 | // The response message containing the greetings 35 | message HelloReply { 36 | string message = 1; 37 | } -------------------------------------------------------------------------------- /examples/openapi/generics/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, Route, Server}; 2 | use poem_openapi::{ 3 | payload::Json, 4 | types::{ParseFromJSON, ToJSON}, 5 | Object, OpenApi, OpenApiService, 6 | }; 7 | 8 | #[derive(Object)] 9 | struct MyObject { 10 | value: T, 11 | } 12 | 13 | struct Api; 14 | 15 | #[OpenApi] 16 | impl Api { 17 | #[oai(path = "/i32", method = "post")] 18 | async fn i32(&self, value: Json>) -> Json> { 19 | value 20 | } 21 | 22 | #[oai(path = "/string", method = "post")] 23 | async fn string(&self, value: Json>) -> Json> { 24 | value 25 | } 26 | } 27 | 28 | #[tokio::main] 29 | async fn main() -> Result<(), std::io::Error> { 30 | if std::env::var_os("RUST_LOG").is_none() { 31 | std::env::set_var("RUST_LOG", "poem=debug"); 32 | } 33 | tracing_subscriber::fmt::init(); 34 | 35 | let api_service = 36 | OpenApiService::new(Api, "Hello World", "1.0").server("http://localhost:3000/api"); 37 | let ui = api_service.swagger_ui(); 38 | 39 | Server::new(TcpListener::bind("0.0.0.0:3000")) 40 | .run(Route::new().nest("/api", api_service).nest("/", ui)) 41 | .await 42 | } 43 | -------------------------------------------------------------------------------- /examples/poem/tls-reload/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, 3 | listener::{Listener, RustlsCertificate, RustlsConfig, TcpListener}, 4 | Route, Server, 5 | }; 6 | use tokio::time::Duration; 7 | 8 | #[handler] 9 | fn index() -> &'static str { 10 | "hello world" 11 | } 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<(), std::io::Error> { 15 | if std::env::var_os("RUST_LOG").is_none() { 16 | std::env::set_var("RUST_LOG", "poem=debug"); 17 | } 18 | tracing_subscriber::fmt::init(); 19 | 20 | let app = Route::new().at("/", get(index)); 21 | 22 | let listener = TcpListener::bind("0.0.0.0:3000").rustls(async_stream::stream! { 23 | loop { 24 | if let Ok(tls_config) = load_tls_config() { 25 | yield tls_config; 26 | } 27 | tokio::time::sleep(Duration::from_secs(60)).await; 28 | } 29 | }); 30 | Server::new(listener).run(app).await 31 | } 32 | 33 | fn load_tls_config() -> Result { 34 | Ok(RustlsConfig::new().fallback( 35 | RustlsCertificate::new() 36 | .cert(std::fs::read("examples/poem/tls-reload/src/cert.pem")?) 37 | .key(std::fs::read("examples/poem/tls-reload/src/key.pem")?), 38 | )) 39 | } 40 | -------------------------------------------------------------------------------- /examples/grpc/helloworld_compressed/proto/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | 21 | package helloworld; 22 | 23 | // The greeting service definition. 24 | service Greeter { 25 | // Sends a greeting 26 | rpc SayHello (HelloRequest) returns (HelloReply) {} 27 | } 28 | 29 | // The request message containing the user's name. 30 | message HelloRequest { 31 | string name = 1; 32 | } 33 | 34 | // The response message containing the greetings 35 | message HelloReply { 36 | string message = 1; 37 | } -------------------------------------------------------------------------------- /examples/grpc/helloworld_typename/proto/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | 21 | package helloworld; 22 | 23 | // The greeting service definition. 24 | service Greeter { 25 | // Sends a greeting 26 | rpc SayHello (HelloRequest) returns (HelloReply) {} 27 | } 28 | 29 | // The request message containing the user's name. 30 | message HelloRequest { 31 | string name = 1; 32 | } 33 | 34 | // The response message containing the greetings 35 | message HelloReply { 36 | string message = 1; 37 | } -------------------------------------------------------------------------------- /examples/poem/acme-http-01/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, 3 | listener::{ 4 | acme::{AutoCert, ChallengeType, LETS_ENCRYPT_PRODUCTION}, 5 | Listener, TcpListener, 6 | }, 7 | middleware::Tracing, 8 | web::Path, 9 | EndpointExt, Route, RouteScheme, Server, 10 | }; 11 | 12 | #[handler] 13 | fn hello(Path(name): Path) -> String { 14 | format!("hello: {name}") 15 | } 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<(), std::io::Error> { 19 | if std::env::var_os("RUST_LOG").is_none() { 20 | std::env::set_var("RUST_LOG", "poem=debug"); 21 | } 22 | tracing_subscriber::fmt::init(); 23 | 24 | let auto_cert = AutoCert::builder() 25 | .directory_url(LETS_ENCRYPT_PRODUCTION) 26 | .domain("poem.rs") 27 | .challenge_type(ChallengeType::Http01) 28 | .build()?; 29 | 30 | let app = RouteScheme::new() 31 | .https(Route::new().at("/hello/:name", get(hello))) 32 | .http(auto_cert.http_01_endpoint()) 33 | .with(Tracing); 34 | 35 | Server::new( 36 | TcpListener::bind("0.0.0.0:443") 37 | .acme(auto_cert) 38 | .combine(TcpListener::bind("0.0.0.0:80")), 39 | ) 40 | .name("hello-world") 41 | .run(app) 42 | .await 43 | } 44 | -------------------------------------------------------------------------------- /examples/openapi/combined-apis/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, Route, Server}; 2 | use poem_openapi::{OpenApi, OpenApiService}; 3 | 4 | struct Api1; 5 | 6 | #[OpenApi] 7 | impl Api1 { 8 | #[oai(path = "/a", method = "get")] 9 | async fn test(&self) {} 10 | } 11 | 12 | struct Api2; 13 | 14 | #[OpenApi] 15 | impl Api2 { 16 | #[oai(path = "/b", method = "post")] 17 | async fn test1(&self) {} 18 | 19 | #[oai(path = "/b", method = "get")] 20 | async fn test2(&self) {} 21 | } 22 | 23 | struct Api3; 24 | 25 | #[OpenApi] 26 | impl Api3 { 27 | #[oai(path = "/c", method = "post")] 28 | async fn test1(&self) {} 29 | 30 | #[oai(path = "/c", method = "get")] 31 | async fn test2(&self) {} 32 | } 33 | 34 | #[tokio::main] 35 | async fn main() -> Result<(), std::io::Error> { 36 | if std::env::var_os("RUST_LOG").is_none() { 37 | std::env::set_var("RUST_LOG", "poem=debug"); 38 | } 39 | tracing_subscriber::fmt::init(); 40 | 41 | let api_service = OpenApiService::new((Api1, Api2, Api3), "Combined APIs", "1.0") 42 | .server("http://localhost:3000/api"); 43 | let ui = api_service.swagger_ui(); 44 | 45 | Server::new(TcpListener::bind("0.0.0.0:3000")) 46 | .run(Route::new().nest("/api", api_service).nest("/", ui)) 47 | .await 48 | } 49 | -------------------------------------------------------------------------------- /poem-grpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! GRPC server for Poem 2 | 3 | #![doc(html_favicon_url = "https://raw.githubusercontent.com/poem-web/poem/master/favicon.ico")] 4 | #![doc(html_logo_url = "https://raw.githubusercontent.com/poem-web/poem/master/logo.png")] 5 | #![forbid(unsafe_code)] 6 | #![deny(unreachable_pub)] 7 | #![cfg_attr(docsrs, feature(doc_cfg))] 8 | #![warn(missing_docs)] 9 | 10 | #[macro_use] 11 | mod macros; 12 | 13 | #[doc(hidden)] 14 | pub mod client; 15 | #[doc(hidden)] 16 | pub mod server; 17 | #[doc(hidden)] 18 | pub mod service; 19 | 20 | pub mod codec; 21 | pub mod metadata; 22 | 23 | mod compression; 24 | mod connector; 25 | mod encoding; 26 | #[cfg(feature = "example_generated")] 27 | pub mod example_generated; 28 | mod health; 29 | mod reflection; 30 | mod request; 31 | mod response; 32 | mod route; 33 | mod status; 34 | mod streaming; 35 | #[cfg(test)] 36 | mod test_harness; 37 | 38 | pub use client::{ClientBuilderError, ClientConfig, ClientConfigBuilder}; 39 | pub use compression::CompressionEncoding; 40 | pub use health::{HealthReporter, ServingStatus, health_service}; 41 | pub use metadata::Metadata; 42 | pub use reflection::Reflection; 43 | pub use request::Request; 44 | pub use response::Response; 45 | pub use route::RouteGrpc; 46 | pub use service::Service; 47 | pub use status::{Code, Status}; 48 | pub use streaming::Streaming; 49 | -------------------------------------------------------------------------------- /poem-grpc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | # [0.5.6] 8 | 9 | - Bump `webpki-roots` to 1.0 10 | 11 | # [0.5.5] 2025-05-03 12 | 13 | - poem-grpc-build: add more methods to config [#1025](https://github.com/poem-web/poem/pull/1025) 14 | 15 | # [0.5.4] 2025-03-24 16 | 17 | - Update MSRV to `1.85.0` 18 | 19 | # [0.5.3] 2025-01-04 20 | 21 | - feat: Implement enable_type_name config method [#924](https://github.com/poem-web/poem/pull/924) 22 | 23 | # [0.5.2] 2024-11-20 24 | 25 | - Add `ClientConfigBuilder::http2_max_header_list_size` method to set the max size of received header frames. 26 | - Update MSRV to `1.81.0` 27 | 28 | # [0.5.1] 2024-09-12 29 | 30 | - set the correct `content-type` for `GrpcClient` 31 | 32 | # [0.5.0] 2024-09-08 33 | 34 | - add support for GRPC compression 35 | 36 | # [0.4.2] 2024-07-19 37 | 38 | - Fix #840: Grpc build emit package when package is empty [#841](https://github.com/poem-web/poem/pull/841) 39 | - chore: bump prost to 0.13 [#849](https://github.com/poem-web/poem/pull/849) 40 | 41 | # [0.4.1] 2024-05-18 42 | 43 | - message can span multiple frame [#817](https://github.com/poem-web/poem/pull/817) -------------------------------------------------------------------------------- /examples/openapi/sse/src/main.rs: -------------------------------------------------------------------------------- 1 | use futures_util::{stream::BoxStream, StreamExt}; 2 | use poem::{listener::TcpListener, Route, Server}; 3 | use poem_openapi::{payload::EventStream, Object, OpenApi, OpenApiService}; 4 | use tokio::time::Duration; 5 | 6 | #[derive(Object)] 7 | struct Event { 8 | value: i32, 9 | } 10 | 11 | struct Api; 12 | 13 | #[OpenApi] 14 | impl Api { 15 | #[oai(path = "/events", method = "get")] 16 | async fn index(&self) -> EventStream> { 17 | EventStream::new( 18 | async_stream::stream! { 19 | for i in 0.. { 20 | tokio::time::sleep(Duration::from_secs(1)).await; 21 | yield Event { value: i }; 22 | } 23 | } 24 | .boxed(), 25 | ) 26 | } 27 | } 28 | 29 | #[tokio::main] 30 | async fn main() -> Result<(), std::io::Error> { 31 | if std::env::var_os("RUST_LOG").is_none() { 32 | std::env::set_var("RUST_LOG", "poem=debug"); 33 | } 34 | tracing_subscriber::fmt::init(); 35 | 36 | let api_service = 37 | OpenApiService::new(Api, "Hello World", "1.0").server("http://localhost:3000/api"); 38 | let ui = api_service.swagger_ui(); 39 | 40 | Server::new(TcpListener::bind("0.0.0.0:3000")) 41 | .run(Route::new().nest("/api", api_service).nest("/", ui)) 42 | .await 43 | } 44 | -------------------------------------------------------------------------------- /poem-openapi/src/docs/tags.md: -------------------------------------------------------------------------------- 1 | Define an OpenAPI Tags. 2 | 3 | # Macro parameters 4 | 5 | | Attribute | Description | Type | Optional | 6 | |------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------| 7 | | rename_all | Rename all the items according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE". | string | Y | 8 | 9 | # Item parameters 10 | 11 | | Attribute | | Description | Type | Optional | 12 | |-----------|---|---------------------|--------|----------| 13 | | rename | | Rename the tag name | string | Y | 14 | | external_docs | Specify a external resource for extended documentation | string | Y | 15 | 16 | # Examples 17 | 18 | ```rust 19 | use poem_openapi::Tags; 20 | 21 | #[derive(Tags)] 22 | enum ApiTags { 23 | /// Operations about user 24 | User, 25 | /// Operations about pet 26 | Pet, 27 | } 28 | ``` -------------------------------------------------------------------------------- /examples/poem/sse/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | use futures_util::StreamExt; 4 | use poem::{ 5 | get, handler, 6 | listener::TcpListener, 7 | web::{ 8 | sse::{Event, SSE}, 9 | Html, 10 | }, 11 | Route, Server, 12 | }; 13 | use tokio::time::Duration; 14 | 15 | #[handler] 16 | fn index() -> Html<&'static str> { 17 | Html( 18 | r#" 19 | 25 | "#, 26 | ) 27 | } 28 | 29 | #[handler] 30 | fn event() -> SSE { 31 | let now = Instant::now(); 32 | SSE::new( 33 | tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1))) 34 | .map(move |_| Event::message(now.elapsed().as_secs().to_string())), 35 | ) 36 | .keep_alive(Duration::from_secs(5)) 37 | } 38 | 39 | #[tokio::main] 40 | async fn main() -> Result<(), std::io::Error> { 41 | if std::env::var_os("RUST_LOG").is_none() { 42 | std::env::set_var("RUST_LOG", "poem=debug"); 43 | } 44 | tracing_subscriber::fmt::init(); 45 | 46 | let app = Route::new().at("/", get(index)).at("/event", get(event)); 47 | Server::new(TcpListener::bind("0.0.0.0:3000")) 48 | .run(app) 49 | .await 50 | } 51 | -------------------------------------------------------------------------------- /examples/disabled/tonic/src/main.rs: -------------------------------------------------------------------------------- 1 | use hello_world::{ 2 | greeter_server::{Greeter, GreeterServer}, 3 | HelloReply, HelloRequest, 4 | }; 5 | use poem::{endpoint::TowerCompatExt, listener::TcpListener, Route, Server}; 6 | use tonic::{Request, Response, Status}; 7 | use tower::buffer::Buffer; 8 | 9 | pub mod hello_world { 10 | tonic::include_proto!("helloworld"); 11 | } 12 | 13 | pub struct MyGreeter; 14 | 15 | impl Greeter for MyGreeter { 16 | async fn say_hello( 17 | &self, 18 | request: Request, 19 | ) -> Result, Status> { 20 | let reply = HelloReply { 21 | message: format!("Hello {}!", request.into_inner().name), 22 | }; 23 | Ok(Response::new(reply)) 24 | } 25 | } 26 | 27 | #[tokio::main] 28 | async fn main() -> Result<(), std::io::Error> { 29 | if std::env::var_os("RUST_LOG").is_none() { 30 | std::env::set_var("RUST_LOG", "poem=debug"); 31 | } 32 | tracing_subscriber::fmt::init(); 33 | 34 | let service = Buffer::new( 35 | tonic::transport::Server::builder() 36 | .add_service(GreeterServer::new(MyGreeter)) 37 | .into_service(), 38 | 1024, 39 | ); 40 | let app = Route::new().nest_no_strip("/", service.compat()); 41 | 42 | Server::new(TcpListener::bind("0.0.0.0:3000")) 43 | .run(app) 44 | .await 45 | } 46 | -------------------------------------------------------------------------------- /poem-openapi/src/types/external/sqlx.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use serde_json::Value; 4 | 5 | use crate::{ 6 | registry::MetaSchemaRef, 7 | types::{ParseError, ParseFromJSON, ParseResult, ToJSON, Type}, 8 | }; 9 | 10 | impl Type for sqlx::types::Json { 11 | const IS_REQUIRED: bool = Self::RawValueType::IS_REQUIRED; 12 | 13 | type RawValueType = T; 14 | 15 | type RawElementValueType = T::RawElementValueType; 16 | 17 | fn name() -> Cow<'static, str> { 18 | Self::RawValueType::name() 19 | } 20 | 21 | fn schema_ref() -> MetaSchemaRef { 22 | Self::RawValueType::schema_ref() 23 | } 24 | 25 | fn as_raw_value(&self) -> Option<&Self::RawValueType> { 26 | Some(&self.0) 27 | } 28 | 29 | fn raw_element_iter<'a>( 30 | &'a self, 31 | ) -> Box + 'a> { 32 | self.0.raw_element_iter() 33 | } 34 | } 35 | 36 | impl ParseFromJSON for sqlx::types::Json { 37 | fn parse_from_json(value: Option) -> ParseResult { 38 | Self::RawValueType::parse_from_json(value) 39 | .map(sqlx::types::Json) 40 | .map_err(ParseError::propagate) 41 | } 42 | } 43 | 44 | impl ToJSON for sqlx::types::Json { 45 | fn to_json(&self) -> Option { 46 | self.0.to_json() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/openapi/auth-basic/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{http::StatusCode, listener::TcpListener, Error, Result, Route}; 2 | use poem_openapi::{auth::Basic, payload::PlainText, OpenApi, OpenApiService, SecurityScheme}; 3 | 4 | /// Basic authorization 5 | /// 6 | /// - User: `test` 7 | /// - Password: `123456` 8 | #[derive(SecurityScheme)] 9 | #[oai(ty = "basic")] 10 | struct MyBasicAuthorization(Basic); 11 | 12 | struct Api; 13 | 14 | #[OpenApi] 15 | impl Api { 16 | #[oai(path = "/basic", method = "get")] 17 | async fn auth_basic(&self, auth: MyBasicAuthorization) -> Result> { 18 | if auth.0.username != "test" || auth.0.password != "123456" { 19 | return Err(Error::from_status(StatusCode::UNAUTHORIZED)); 20 | } 21 | Ok(PlainText(format!("hello: {}", auth.0.username))) 22 | } 23 | } 24 | 25 | #[tokio::main] 26 | async fn main() -> Result<(), std::io::Error> { 27 | if std::env::var_os("RUST_LOG").is_none() { 28 | std::env::set_var("RUST_LOG", "poem=debug"); 29 | } 30 | tracing_subscriber::fmt::init(); 31 | 32 | let api_service = 33 | OpenApiService::new(Api, "Authorization Demo", "1.0").server("http://localhost:3000/api"); 34 | let ui = api_service.swagger_ui(); 35 | 36 | poem::Server::new(TcpListener::bind("0.0.0.0:3000")) 37 | .run(Route::new().nest("/api", api_service).nest("/", ui)) 38 | .await 39 | } 40 | -------------------------------------------------------------------------------- /poem-openapi/src/path_util.rs: -------------------------------------------------------------------------------- 1 | fn normalize_path(path: &str) -> String { 2 | if path.is_empty() { 3 | "/".to_string() 4 | } else if !path.starts_with('/') { 5 | format!("/{path}") 6 | } else { 7 | path.to_string() 8 | } 9 | } 10 | 11 | #[doc(hidden)] 12 | pub fn join_path(base: &str, path: &str) -> String { 13 | let base = normalize_path(base); 14 | let path = normalize_path(path); 15 | 16 | if path == "/" { 17 | return base; 18 | } 19 | 20 | if base.ends_with('/') { 21 | base + path.trim_start_matches('/') 22 | } else { 23 | base + &path 24 | } 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::*; 30 | 31 | #[test] 32 | fn test_join_path() { 33 | assert_eq!(join_path("", "/abc"), "/abc"); 34 | assert_eq!(join_path("/abc", "/def"), "/abc/def"); 35 | assert_eq!(join_path("/abc", "def"), "/abc/def"); 36 | assert_eq!(join_path("abc/def", "ghi"), "/abc/def/ghi"); 37 | assert_eq!(join_path("/", "/ghi"), "/ghi"); 38 | assert_eq!(join_path("/", "/"), "/"); 39 | assert_eq!(join_path("/abc", ""), "/abc"); 40 | assert_eq!(join_path("", ""), "/"); 41 | assert_eq!(join_path("/abc/", "/"), "/abc/"); 42 | assert_eq!(join_path("/abc/", "/def"), "/abc/def"); 43 | assert_eq!(join_path("/abc/", "/def/"), "/abc/def/"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /poem-openapi/src/ui/openapi_explorer/mod.rs: -------------------------------------------------------------------------------- 1 | use poem::{Endpoint, endpoint::make_sync, web::Html}; 2 | 3 | const REDOC_JS: &str = include_str!("openapi-explorer.min.js"); 4 | 5 | const REDOC_TEMPLATE: &str = r#" 6 | 7 | 8 | 9 | OpenAPI Explorer 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | "#; 32 | 33 | pub(crate) fn create_html(document: &str) -> String { 34 | REDOC_TEMPLATE 35 | .replace("{:script}", REDOC_JS) 36 | .replace("{:spec}", document) 37 | } 38 | 39 | pub(crate) fn create_endpoint(document: String) -> impl Endpoint + 'static { 40 | let ui_html = create_html(&document); 41 | poem::Route::new().at("/", make_sync(move |_| Html(ui_html.clone()))) 42 | } 43 | -------------------------------------------------------------------------------- /poem/src/listener/acme/keypair.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Error as IoError, Result as IoResult}; 2 | 3 | use ring::{ 4 | rand::SystemRandom, 5 | signature::{ECDSA_P256_SHA256_FIXED_SIGNING, EcdsaKeyPair, KeyPair as _, Signature}, 6 | }; 7 | 8 | pub(crate) struct KeyPair(EcdsaKeyPair); 9 | 10 | impl KeyPair { 11 | pub(crate) fn from_pkcs8(pkcs8: impl AsRef<[u8]>) -> IoResult { 12 | let rng = SystemRandom::new(); 13 | EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_ref(), &rng) 14 | .map(KeyPair) 15 | .map_err(|_| IoError::other("failed to load key pair")) 16 | } 17 | 18 | fn generate_pkcs8() -> IoResult> { 19 | let alg = &ECDSA_P256_SHA256_FIXED_SIGNING; 20 | let rng = SystemRandom::new(); 21 | EcdsaKeyPair::generate_pkcs8(alg, &rng) 22 | .map_err(|_| IoError::other("failed to generate acme key pair")) 23 | } 24 | 25 | pub(crate) fn generate() -> IoResult { 26 | Self::from_pkcs8(Self::generate_pkcs8()?) 27 | } 28 | 29 | pub(crate) fn sign(&self, message: impl AsRef<[u8]>) -> IoResult { 30 | self.0 31 | .sign(&SystemRandom::new(), message.as_ref()) 32 | .map_err(|_| IoError::other("failed to sign message")) 33 | } 34 | 35 | pub(crate) fn public_key(&self) -> &[u8] { 36 | self.0.public_key().as_ref() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/grpc/reflection/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, middleware::Tracing, EndpointExt, Server}; 2 | use poem_grpc::{Reflection, Request, Response, RouteGrpc, Status}; 3 | 4 | poem_grpc::include_proto!("helloworld"); 5 | const FILE_DESCRIPTOR_SET: &[u8] = poem_grpc::include_file_descriptor_set!("helloworld.bin"); 6 | 7 | struct GreeterService; 8 | 9 | impl Greeter for GreeterService { 10 | async fn say_hello( 11 | &self, 12 | request: Request, 13 | ) -> Result, Status> { 14 | let reply = HelloReply { 15 | message: format!("Hello {}!", request.into_inner().name), 16 | }; 17 | Ok(Response::new(reply)) 18 | } 19 | } 20 | 21 | #[tokio::main] 22 | async fn main() -> Result<(), std::io::Error> { 23 | if std::env::var_os("RUST_LOG").is_none() { 24 | std::env::set_var("RUST_LOG", "poem=debug"); 25 | } 26 | tracing_subscriber::fmt::init(); 27 | 28 | Server::new(TcpListener::bind("0.0.0.0:3000")) 29 | .run( 30 | RouteGrpc::new() 31 | .add_service( 32 | Reflection::new() 33 | .add_file_descriptor_set(FILE_DESCRIPTOR_SET) 34 | .build(), 35 | ) 36 | .add_service(GreeterServer::new(GreeterService)) 37 | .with(Tracing), 38 | ) 39 | .await 40 | } 41 | -------------------------------------------------------------------------------- /examples/poem/async-graphql/src/main.rs: -------------------------------------------------------------------------------- 1 | mod starwars; 2 | 3 | use async_graphql::{ 4 | http::{playground_source, GraphQLPlaygroundConfig}, 5 | EmptyMutation, EmptySubscription, Request, Response, Schema, 6 | }; 7 | use poem::{ 8 | get, handler, 9 | listener::TcpListener, 10 | web::{Data, Html, Json}, 11 | EndpointExt, IntoResponse, Route, Server, 12 | }; 13 | use starwars::{QueryRoot, StarWars, StarWarsSchema}; 14 | 15 | #[handler] 16 | async fn graphql_handler(schema: Data<&StarWarsSchema>, req: Json) -> Json { 17 | Json(schema.execute(req.0).await) 18 | } 19 | 20 | #[handler] 21 | fn graphql_playground() -> impl IntoResponse { 22 | Html(playground_source(GraphQLPlaygroundConfig::new("/"))) 23 | } 24 | 25 | #[tokio::main] 26 | async fn main() -> Result<(), std::io::Error> { 27 | if std::env::var_os("RUST_LOG").is_none() { 28 | std::env::set_var("RUST_LOG", "poem=debug"); 29 | } 30 | tracing_subscriber::fmt::init(); 31 | 32 | let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription) 33 | .data(StarWars::new()) 34 | .finish(); 35 | 36 | let app = Route::new() 37 | .at("/", get(graphql_playground).post(graphql_handler)) 38 | .data(schema); 39 | 40 | println!("Playground: http://localhost:3000"); 41 | 42 | Server::new(TcpListener::bind("0.0.0.0:3000")) 43 | .run(app) 44 | .await 45 | } 46 | -------------------------------------------------------------------------------- /poem/src/endpoint/catch_error.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, marker::PhantomData}; 2 | 3 | use crate::{Endpoint, IntoResponse, Request, Response, Result}; 4 | 5 | /// Endpoint for the [`catch_error`](super::EndpointExt::catch_error) method. 6 | pub struct CatchError { 7 | inner: E, 8 | f: F, 9 | _mark1: PhantomData, 10 | _mark2: PhantomData, 11 | } 12 | 13 | impl CatchError { 14 | #[inline] 15 | pub(crate) fn new(inner: E, f: F) -> CatchError { 16 | Self { 17 | inner, 18 | f, 19 | _mark1: PhantomData, 20 | _mark2: PhantomData, 21 | } 22 | } 23 | } 24 | 25 | impl Endpoint for CatchError 26 | where 27 | E: Endpoint, 28 | F: Fn(ErrType) -> Fut + Send + Sync, 29 | Fut: Future + Send, 30 | R: IntoResponse + Send + Sync, 31 | ErrType: std::error::Error + Send + Sync + 'static, 32 | { 33 | type Output = Response; 34 | 35 | async fn call(&self, req: Request) -> Result { 36 | match self.inner.call(req).await { 37 | Ok(resp) => Ok(resp.into_response()), 38 | Err(err) if err.is::() => Ok((self.f)(err.downcast::().unwrap()) 39 | .await 40 | .into_response()), 41 | Err(err) => Err(err), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /poem-openapi/src/ui/redoc/mod.rs: -------------------------------------------------------------------------------- 1 | use poem::{Endpoint, endpoint::make_sync, web::Html}; 2 | 3 | const REDOC_JS: &str = include_str!("redoc.standalone.js"); 4 | 5 | const REDOC_TEMPLATE: &str = r#" 6 | 7 | 8 | 9 | Redoc 10 | 11 | 12 | 13 | 14 | 15 | 18 | 24 | 25 | 26 | 27 |
28 | 29 | 35 | 36 | 37 | "#; 38 | 39 | pub(crate) fn create_html(document: &str) -> String { 40 | REDOC_TEMPLATE 41 | .replace("{:script}", REDOC_JS) 42 | .replace("{:spec}", document) 43 | } 44 | 45 | pub(crate) fn create_endpoint(document: String) -> impl Endpoint { 46 | let ui_html = create_html(&document); 47 | poem::Route::new().at("/", make_sync(move |_| Html(ui_html.clone()))) 48 | } 49 | -------------------------------------------------------------------------------- /poem/src/endpoint/mod.rs: -------------------------------------------------------------------------------- 1 | //! Endpoint related types. 2 | 3 | mod after; 4 | mod and_then; 5 | mod around; 6 | mod before; 7 | mod catch_all_error; 8 | mod catch_error; 9 | #[cfg(feature = "embed")] 10 | mod embed; 11 | #[allow(clippy::module_inception)] 12 | mod endpoint; 13 | mod inspect_all_err; 14 | mod inspect_err; 15 | mod map; 16 | mod map_to_response; 17 | #[cfg(feature = "prometheus")] 18 | mod prometheus_exporter; 19 | #[cfg(feature = "static-files")] 20 | mod static_files; 21 | mod to_response; 22 | #[cfg(feature = "tower-compat")] 23 | mod tower_compat; 24 | 25 | pub use after::After; 26 | pub use and_then::AndThen; 27 | pub use around::Around; 28 | pub use before::Before; 29 | pub use catch_all_error::CatchAllError; 30 | pub use catch_error::CatchError; 31 | #[cfg(feature = "embed")] 32 | pub use embed::{EmbeddedFileEndpoint, EmbeddedFilesEndpoint}; 33 | pub use endpoint::{ 34 | BoxEndpoint, DynEndpoint, EitherEndpoint, Endpoint, EndpointExt, IntoEndpoint, ToDynEndpoint, 35 | make, make_sync, 36 | }; 37 | pub use inspect_all_err::InspectAllError; 38 | pub use inspect_err::InspectError; 39 | pub use map::Map; 40 | pub use map_to_response::MapToResponse; 41 | #[cfg(feature = "prometheus")] 42 | pub use prometheus_exporter::PrometheusExporter; 43 | #[cfg(feature = "static-files")] 44 | pub use static_files::{StaticFileEndpoint, StaticFilesEndpoint}; 45 | pub use to_response::ToResponse; 46 | #[cfg(feature = "tower-compat")] 47 | pub use tower_compat::TowerCompatExt; 48 | -------------------------------------------------------------------------------- /poem-worker/src/body.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use http_body::{Body, Frame, SizeHint}; 8 | 9 | pub struct WorkerBody(pub(crate) worker::Body); 10 | 11 | impl Body for WorkerBody { 12 | type Data = bytes::Bytes; 13 | type Error = io::Error; 14 | 15 | #[inline] 16 | fn poll_frame( 17 | self: Pin<&mut Self>, 18 | cx: &mut Context<'_>, 19 | ) -> Poll, Self::Error>>> { 20 | let body = self.get_mut(); 21 | 22 | let inner = Pin::new(&mut body.0); 23 | 24 | let res = inner.poll_frame(cx); 25 | 26 | match res { 27 | Poll::Pending => Poll::Pending, 28 | Poll::Ready(None) => Poll::Ready(None), 29 | Poll::Ready(Some(Ok(r))) => Poll::Ready(Some(Ok(r))), 30 | Poll::Ready(Some(Err(e))) => match e { 31 | worker::Error::Io(e) => Poll::Ready(Some(Err(io::Error::other(e)))), 32 | _ => Poll::Ready(Some(Err(io::Error::other(e)))), 33 | }, 34 | } 35 | } 36 | 37 | fn size_hint(&self) -> SizeHint { 38 | self.0.size_hint() 39 | } 40 | 41 | fn is_end_stream(&self) -> bool { 42 | self.0.is_end_stream() 43 | } 44 | } 45 | 46 | pub fn build_worker_body(body: poem::Body) -> Result { 47 | let stream = body.into_bytes_stream(); 48 | 49 | worker::Body::from_stream(stream) 50 | } 51 | -------------------------------------------------------------------------------- /examples/openapi/log-with-operation-id/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, Endpoint, EndpointExt, Route, Server}; 2 | use poem_openapi::{param::Query, payload::PlainText, OpenApi, OpenApiService, OperationId}; 3 | 4 | struct Api; 5 | 6 | #[OpenApi] 7 | impl Api { 8 | #[oai(path = "/hello", method = "get", operation_id = "index-get")] 9 | async fn index(&self, name: Query>) -> PlainText { 10 | match name.0 { 11 | Some(name) => PlainText(format!("hello, {name}!")), 12 | None => PlainText("hello!".to_string()), 13 | } 14 | } 15 | } 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<(), std::io::Error> { 19 | let api_service = 20 | OpenApiService::new(Api, "Hello World", "1.0").server("http://localhost:3000/api"); 21 | let ui = api_service.swagger_ui(); 22 | let app = Route::new() 23 | .nest("/api", api_service) 24 | .nest("/", ui) 25 | .around(|ep, req| async move { 26 | let uri = req.uri().clone(); 27 | let resp = ep.get_response(req).await; 28 | 29 | if let Some(operation_id) = resp.data::() { 30 | println!("[{}]{} {}", operation_id, uri, resp.status()); 31 | } else { 32 | println!("{} {}", uri, resp.status()); 33 | } 34 | 35 | Ok(resp) 36 | }); 37 | 38 | Server::new(TcpListener::bind("0.0.0.0:3000")) 39 | .run(app) 40 | .await 41 | } 42 | -------------------------------------------------------------------------------- /examples/openapi/union/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, Route, Server}; 2 | use poem_openapi::{payload::Json, Object, OpenApi, OpenApiService, Union}; 3 | 4 | #[derive(Object, Debug, PartialEq)] 5 | struct A { 6 | v1: i32, 7 | v2: String, 8 | } 9 | 10 | #[derive(Object, Debug, PartialEq)] 11 | struct B { 12 | v3: f32, 13 | } 14 | 15 | #[derive(Union, Debug, PartialEq)] 16 | #[oai(discriminator_name = "type")] 17 | enum MyObj { 18 | A(A), 19 | B(B), 20 | } 21 | 22 | struct Api; 23 | 24 | #[OpenApi] 25 | impl Api { 26 | #[oai(path = "/put", method = "post")] 27 | async fn index(&self, obj: Json) -> Json { 28 | obj 29 | } 30 | } 31 | 32 | #[tokio::main] 33 | async fn main() -> Result<(), std::io::Error> { 34 | if std::env::var_os("RUST_LOG").is_none() { 35 | std::env::set_var("RUST_LOG", "poem=debug"); 36 | } 37 | tracing_subscriber::fmt::init(); 38 | 39 | let api_service = OpenApiService::new(Api, "Union", "1.0").server("http://localhost:3000/api"); 40 | let ui = api_service.swagger_ui(); 41 | let spec = api_service.spec_endpoint(); 42 | let spec_yaml = api_service.spec_endpoint_yaml(); 43 | 44 | Server::new(TcpListener::bind("0.0.0.0:3000")) 45 | .run( 46 | Route::new() 47 | .nest("/api", api_service) 48 | .nest("/", ui) 49 | .at("/spec", spec) 50 | .at("/spec_yaml", spec_yaml), 51 | ) 52 | .await 53 | } 54 | -------------------------------------------------------------------------------- /poem-mcpserver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poem-mcpserver" 3 | version = "0.3.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | documentation.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | rust-version.workspace = true 11 | readme = "README.md" 12 | description = "MCP Server implementation for Poem" 13 | keywords = ["framework", "async", "mcp", "ai"] 14 | categories = [ 15 | "network-programming", 16 | "asynchronous", 17 | "web-programming::http-server", 18 | "web-programming::websocket", 19 | ] 20 | 21 | [features] 22 | streamable-http = ["dep:poem"] 23 | 24 | [dependencies] 25 | poem-mcpserver-macros.workspace = true 26 | 27 | schemars.workspace = true 28 | serde = { workspace = true, features = ["derive"] } 29 | serde_json.workspace = true 30 | time = { workspace = true, features = ["macros", "formatting", "parsing"] } 31 | tokio = { workspace = true, features = ["io-std", "io-util", "rt", "net"] } 32 | poem = { workspace = true, features = ["sse"], optional = true } 33 | rand.workspace = true 34 | tokio-stream.workspace = true 35 | async-stream.workspace = true 36 | tracing.workspace = true 37 | pin-project-lite = "0.2.16" 38 | itertools = "0.14.0" 39 | mime.workspace = true 40 | base64.workspace = true 41 | 42 | [dev-dependencies] 43 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 44 | 45 | [package.metadata.docs.rs] 46 | all-features = true 47 | rustdoc-args = ["--cfg", "docsrs"] 48 | -------------------------------------------------------------------------------- /examples/poem/add-data/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | sync::{Arc, Mutex}, 4 | }; 5 | 6 | use poem::{ 7 | get, handler, 8 | listener::TcpListener, 9 | middleware::AddData, 10 | web::{Data, Path}, 11 | EndpointExt, Route, Server, 12 | }; 13 | 14 | struct AppState { 15 | clients: Mutex>, 16 | } 17 | 18 | #[handler] 19 | fn set_state(Path(name): Path, state: Data<&Arc>) -> String { 20 | let mut store = state.clients.lock().unwrap(); 21 | store.insert(name.to_string(), "some state object".to_string()); 22 | "store updated".to_string() 23 | } 24 | 25 | #[handler] 26 | fn get_state(Path(name): Path, state: Data<&Arc>) -> String { 27 | let store = state.clients.lock().unwrap(); 28 | let message = store.get(&name).unwrap(); 29 | message.to_string() 30 | } 31 | 32 | #[tokio::main] 33 | async fn main() -> Result<(), std::io::Error> { 34 | if std::env::var_os("RUST_LOG").is_none() { 35 | std::env::set_var("RUST_LOG", "poem=debug"); 36 | } 37 | tracing_subscriber::fmt::init(); 38 | 39 | let state = Arc::new(AppState { 40 | clients: Mutex::new(HashMap::new()), 41 | }); 42 | 43 | let app = Route::new() 44 | .at("/hello/:name", get(set_state)) 45 | .at("/:name", get(get_state)) 46 | .with(AddData::new(state)); 47 | 48 | Server::new(TcpListener::bind("0.0.0.0:3000")) 49 | .name("add-data") 50 | .run(app) 51 | .await 52 | } 53 | -------------------------------------------------------------------------------- /examples/poem/tls-reload/src/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEADCCAmigAwIBAgICAcgwDQYJKoZIhvcNAQELBQAwLDEqMCgGA1UEAwwhcG9u 3 | eXRvd24gUlNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE2MDgxMzE2MDcwNFoX 4 | DTIyMDIwMzE2MDcwNFowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wggEiMA0G 5 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpVhh1/FNP2qvWenbZSghari/UThwe 6 | dynfnHG7gc3JmygkEdErWBO/CHzHgsx7biVE5b8sZYNEDKFojyoPHGWK2bQM/FTy 7 | niJCgNCLdn6hUqqxLAml3cxGW77hAWu94THDGB1qFe+eFiAUnDmob8gNZtAzT6Ky 8 | b/JGJdrEU0wj+Rd7wUb4kpLInNH/Jc+oz2ii2AjNbGOZXnRz7h7Kv3sO9vABByYe 9 | LcCj3qnhejHMqVhbAT1MD6zQ2+YKBjE52MsQKU/xhUpu9KkUyLh0cxkh3zrFiKh4 10 | Vuvtc+n7aeOv2jJmOl1dr0XLlSHBlmoKqH6dCTSbddQLmlK7dms8vE01AgMBAAGj 11 | gb4wgbswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFMeUzGYV 12 | bXwJNQVbY1+A8YXYZY8pMEIGA1UdIwQ7MDmAFJvEsUi7+D8vp8xcWvnEdVBGkpoW 13 | oR6kHDAaMRgwFgYDVQQDDA9wb255dG93biBSU0EgQ0GCAXswOwYDVR0RBDQwMoIO 14 | dGVzdHNlcnZlci5jb22CFXNlY29uZC50ZXN0c2VydmVyLmNvbYIJbG9jYWxob3N0 15 | MA0GCSqGSIb3DQEBCwUAA4IBgQBsk5ivAaRAcNgjc7LEiWXFkMg703AqDDNx7kB1 16 | RDgLalLvrjOfOp2jsDfST7N1tKLBSQ9bMw9X4Jve+j7XXRUthcwuoYTeeo+Cy0/T 17 | 1Q78ctoX74E2nB958zwmtRykGrgE/6JAJDwGcgpY9kBPycGxTlCN926uGxHsDwVs 18 | 98cL6ZXptMLTR6T2XP36dAJZuOICSqmCSbFR8knc/gjUO36rXTxhwci8iDbmEVaf 19 | BHpgBXGU5+SQ+QM++v6bHGf4LNQC5NZ4e4xvGax8ioYu/BRsB/T3Lx+RlItz4zdU 20 | XuxCNcm3nhQV2ZHquRdbSdoyIxV5kJXel4wCmOhWIq7A2OBKdu5fQzIAzzLi65EN 21 | RPAKsKB4h7hGgvciZQ7dsMrlGw0DLdJ6UrFyiR5Io7dXYT/+JP91lP5xsl6Lhg9O 22 | FgALt7GSYRm2cZdgi9pO9rRr83Br1VjQT1vHz6yoZMXSqc4A2zcN2a2ZVq//rHvc 23 | FZygs8miAhWPzqnpmgTj1cPiU1M= 24 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /poem/src/listener/certs/cert1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEADCCAmigAwIBAgICAcgwDQYJKoZIhvcNAQELBQAwLDEqMCgGA1UEAwwhcG9u 3 | eXRvd24gUlNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTI1MDQyMzA1MTkxMloX 4 | DTMwMTAxNDA1MTkxMlowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wggEiMA0G 5 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCJ572rsPRxr9pFi/VXJRSH86dGQG9J 6 | pBvvg4NbSCHBFHyF9xNK8zv2K8ELbTYJ11Jf4Uv8SlJkg4LLRZyP5phi1vLRAXHl 7 | EPkeN+N5tMNRYuT9IL5EdT1uhtUml2yqIYqRqMwD3yDqJxMc8BpyWtVykEtxa/3X 8 | UiiakS94BAZu1Ma8OQwPp30dMzmO7Xp7iFgTvbIymCjEUtoGD9j5JvEpt21DxGGp 9 | WzW4KUJsE5gGwRudlmlGCiyqLEcA4KfhfShuWLW2dEhWT5CvbH+UU4rWjVsZekkE 10 | Hw77y+5SIZ3lK3UElJ5sfOmMcDPeUC0Fvrg1wiaNH5ga97dn9/UFn+fLAgMBAAGj 11 | gb4wgbswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFNSQCHio 12 | YF5tpSt7ZHituaf8mJD4MEIGA1UdIwQ7MDmAFGer2aMHDiGG5Cym2VPf/xIBX/yD 13 | oR6kHDAaMRgwFgYDVQQDDA9wb255dG93biBSU0EgQ0GCAXswOwYDVR0RBDQwMoIO 14 | dGVzdHNlcnZlci5jb22CFXNlY29uZC50ZXN0c2VydmVyLmNvbYIJbG9jYWxob3N0 15 | MA0GCSqGSIb3DQEBCwUAA4IBgQBfp6C5HUnFFnOe6/QACnQPNI80hnelkt2j1jtq 16 | PuaCAY3+pLlNDgIx2PLQbxp4IshX2dhr9kLIUzb3Y6GP8gy4GNeALS56pE2feKJw 17 | T3A9cgLHtM4EDaEZYJQ8/tq6G2j9yltQs4myin9Dd9Bzqnk9fqI/wDj1lspUP1X5 18 | agf+CeY366Zc1hxHtP8fr0ckuvEa491pwr6vMnLP/ahCofFHcbzvGDjreJiI1auK 19 | VkYzRUwX8MnEyQU6cq6YxihId+Fj/IdQuLKquEg/DMHAJPz/O87501T6CX+5IkUL 20 | c+OQF1BwsaJEt+W8DuWXjuDqXqrSs0a02N7wof1y2xhZB9n3HQTKKZch+FG4Hhhw 21 | oWayUHYpDNm9Rv+/M2ckfm1jsuYRYJ8ttKe4hhLzrL7oT4Yd15c8CR1N8lMy2QBB 22 | ab0YJ/a1JcBpgXxXHSZ83pM5GwtvcpzpD/HJffiG1rKXKw0DZSTAbWH3xyk7KdrF 23 | HDv2B/qT2Hwy5gINA8ZkaXA52uM= 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /examples/mcpserver/counter-streamable-http/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{listener::TcpListener, middleware::Cors, EndpointExt, Route, Server}; 2 | use poem_mcpserver::{content::Text, streamable_http, McpServer, Tools}; 3 | 4 | struct Counter { 5 | count: i32, 6 | } 7 | 8 | /// This server provides a counter tool that can increment and decrement values. 9 | /// 10 | /// The counter starts at 0 and can be modified using the 'increment' and 11 | /// 'decrement' tools. Use 'get_value' to check the current count. 12 | #[Tools] 13 | impl Counter { 14 | /// Increment the counter by 1 15 | async fn increment(&mut self) -> Text { 16 | self.count += 1; 17 | Text(self.count) 18 | } 19 | 20 | /// Decrement the counter by 1 21 | async fn decrement(&mut self) -> Text { 22 | self.count -= 1; 23 | Text(self.count) 24 | } 25 | 26 | /// Get the current counter value 27 | async fn get_value(&self) -> Text { 28 | Text(self.count) 29 | } 30 | } 31 | 32 | #[tokio::main] 33 | async fn main() -> std::io::Result<()> { 34 | if std::env::var_os("RUST_LOG").is_none() { 35 | std::env::set_var("RUST_LOG", "poem=debug"); 36 | } 37 | tracing_subscriber::fmt::init(); 38 | 39 | let listener = TcpListener::bind("127.0.0.1:8000"); 40 | let app = Route::new() 41 | .at( 42 | "/", 43 | streamable_http::endpoint(|_| McpServer::new().tools(Counter { count: 0 })), 44 | ) 45 | .with(Cors::new()); 46 | Server::new(listener).run(app).await 47 | } 48 | -------------------------------------------------------------------------------- /examples/poem/middleware/src/main.rs: -------------------------------------------------------------------------------- 1 | use poem::{ 2 | get, handler, listener::TcpListener, Endpoint, EndpointExt, IntoResponse, Middleware, Request, 3 | Response, Result, Route, Server, 4 | }; 5 | 6 | struct Log; 7 | 8 | impl Middleware for Log { 9 | type Output = LogImpl; 10 | 11 | fn transform(&self, ep: E) -> Self::Output { 12 | LogImpl(ep) 13 | } 14 | } 15 | 16 | struct LogImpl(E); 17 | 18 | impl Endpoint for LogImpl { 19 | type Output = Response; 20 | 21 | async fn call(&self, req: Request) -> Result { 22 | println!("request: {}", req.uri().path()); 23 | let res = self.0.call(req).await; 24 | 25 | match res { 26 | Ok(resp) => { 27 | let resp = resp.into_response(); 28 | println!("response: {}", resp.status()); 29 | Ok(resp) 30 | } 31 | Err(err) => { 32 | println!("error: {err}"); 33 | Err(err) 34 | } 35 | } 36 | } 37 | } 38 | 39 | #[handler] 40 | fn index() -> String { 41 | "hello".to_string() 42 | } 43 | 44 | #[tokio::main] 45 | async fn main() -> Result<(), std::io::Error> { 46 | if std::env::var_os("RUST_LOG").is_none() { 47 | std::env::set_var("RUST_LOG", "poem=debug"); 48 | } 49 | tracing_subscriber::fmt::init(); 50 | 51 | let app = Route::new().at("/", get(index)).with(Log); 52 | Server::new(TcpListener::bind("0.0.0.0:3000")) 53 | .run(app) 54 | .await 55 | } 56 | -------------------------------------------------------------------------------- /poem-openapi/src/auth/api_key.rs: -------------------------------------------------------------------------------- 1 | use poem::{Request, Result}; 2 | 3 | use crate::{ 4 | auth::ApiKeyAuthorization, base::UrlQuery, error::AuthorizationError, registry::MetaParamIn, 5 | }; 6 | 7 | /// Used to extract the Api Key from the request. 8 | #[derive(Debug)] 9 | pub struct ApiKey { 10 | /// Api key 11 | pub key: String, 12 | } 13 | 14 | impl ApiKeyAuthorization for ApiKey { 15 | fn from_request( 16 | req: &Request, 17 | query: &UrlQuery, 18 | name: &str, 19 | in_type: MetaParamIn, 20 | ) -> Result { 21 | match in_type { 22 | MetaParamIn::Query => query 23 | .get(name) 24 | .cloned() 25 | .map(|value| Self { key: value }) 26 | .ok_or_else(|| AuthorizationError.into()), 27 | MetaParamIn::Header => req 28 | .headers() 29 | .get(name) 30 | .and_then(|value| value.to_str().ok()) 31 | .map(|value| Self { 32 | key: value.to_string(), 33 | }) 34 | .ok_or_else(|| AuthorizationError.into()), 35 | #[cfg(feature = "cookie")] 36 | MetaParamIn::Cookie => req 37 | .cookie() 38 | .get(name) 39 | .as_ref() 40 | .map(|cookie| Self { 41 | key: cookie.value_str().to_string(), 42 | }) 43 | .ok_or_else(|| AuthorizationError.into()), 44 | _ => unreachable!(), 45 | } 46 | } 47 | } 48 | --------------------------------------------------------------------------------