Начало работы с сервером

Давайте начнем с создания сервера "Hello, World!" и расширим его функциональность.

Первым делом нам нужно объявить наши зависимости. Добавим следующее в наш Cargo.toml:

[dependencies]
hyper = { version = "1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1"
hyper-util = { version = "0.1", features = ["full"] }

Затем нам нужно добавить несколько импортов в наш файл main.rs:

extern crate tokio;
extern crate hyper;
extern crate http_body_util;
extern crate hyper_util;
use std::convert::Infallible;
use std::net::SocketAddr;

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
fn main() {}

Создание Сервиса (Service)

[Service][service] позволяет нам определить, как наш сервер будет отвечать на входящие запросы. Он представляет собой асинхронную функцию, которая принимает [Request][request] и возвращает Future. Когда обработка этого future завершается, он разрешается в [Response][response] или ошибку.

Hyper предоставляет утилиту для создания Service из функции, которая должна покрыть большинство случаев использования: [service_fn][service_fn]. Мы используем её, чтобы создать сервис из нашей функции hello, когда будем готовы запустить сервер.

extern crate hyper;
extern crate http_body_util;
use std::convert::Infallible;
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Request, Response};
fn main() {}
async fn hello(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
    Ok(Response::new(Full::new(Bytes::from("Hello, World!"))))
}

Используя эту функцию в качестве сервиса, мы указываем нашему серверу отвечать на все запросы статусом 200 OK по умолчанию. Тело ответа Body будет содержать наше дружеское приветствие в виде одного фрагмента байтов (chunk of bytes), а заголовок Content-Length будет установлен автоматически.

Запуск сервера

Наконец, нам нужно подключить наш сервис hello к работающему серверу Hyper.

Мы углубимся в детали некоторых из этих вещей в другом руководстве.

extern crate tokio;
extern crate hyper;
extern crate http_body_util;
extern crate hyper_util;
mod no_run {
use std::convert::Infallible;
use std::net::SocketAddr;

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
async fn hello(
    _: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
    Ok(Response::new(Full::new(Bytes::from("Hello World!"))))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    // Мы создаем TcpListener и привязываем его к 127.0.0.1:3000
    let listener = TcpListener::bind(addr).await?;

    // Мы запускаем цикл для непрерывного принятия входящих соединений
    loop {
        let (stream, _) = listener.accept().await?;

        // Используем адаптер для доступа к чему-то, реализующему трейты `tokio::io`, как если бы они реализовывали
        // трейты ввода-вывода `hyper::rt`.
        let io = TokioIo::new(stream);

        // Порождаем задачу tokio для конкурентного обслуживания множественных соединений
        tokio::task::spawn(async move {
            // Наконец, мы привязываем входящее соединение к нашему сервису `hello`
            if let Err(err) = http1::Builder::new()
                // `service_fn` преобразует нашу функцию в `Service`
                .serve_connection(io, service_fn(hello))
                .await
            {
                eprintln!("Ошибка при обслуживании соединения: {:?}", err);
            }
        });
    }
}
}
fn main() {}

Чтобы увидеть все фрагменты кода, собранные вместе, ознакомьтесь с [полным примером][example]!

Кроме того, если service_fn не удовлетворяет вашим требованиям и вы хотите реализовать Service самостоятельно, см. этот [пример][impl service].

HTTP/2

Этот пример использует модуль http1 для создания сервера, который работает по протоколу HTTP/1. Если вы хотите использовать HTTP/2, вы можете использовать модуль http2 с небольшими изменениями по сравнению с сервером http1. Сборщик (builder) для http2 требует для работы исполнитель (executor). Исполнитель должен реализовывать трейт hyper::rt::Executor.

Чтобы реализовать Executor, ознакомьтесь с примером [runtime][runtime]. Чтобы увидеть полный пример с HTTP/2, ознакомьтесь с [полным примером][example_http2]!

[service]: {{ site.hyper_docs_url }}/hyper/service/trait.Service.html [service_fn]: {{ site.hyper_docs_url }}/hyper/service/fn.service_fn.html [request]: {{ site.hyper_docs_url }}/hyper/struct.Request.html [response]: {{ site.hyper_docs_url }}/hyper/struct.Response.html [parts]: {{ site.http_docs_url }}/http/response/struct.Parts.html [example]: {{ site.examples_url }}/hello.rs [example_http2]: {{ site.examples_url }}/hello-http2.rs [runtime]: ../init/runtime.md [impl service]: {{ site.examples_url }}/service_struct_impl.rs