总览(Overview)

This post is for anyone interested in writing performant and safe applications in Rust quickly. It walks the reader through designing and implementing an HTTP Tunnel and basic, language-agnostic, principles of creating robust, scalable, observable, and evolvable network applications.

这篇文章供有兴趣在Rust中快速编写高性能和安全应用程序的任何人使用。 它引导读者设计和实现HTTP隧道以及创建健壮,可伸缩,可观察和可发展的网络应用程序的基本,与语言无关的原理。

防锈:性能,可靠性,生产率。 选择三个。 (Rust: performance, reliability, productivity. Pick three.)

About a year ago, I started to learn Rust. The first two weeks were quite painful. Nothing compiled, I didn’t know how to do basic operations, I couldn’t make a simple program run. But step by step, I started to understand what the compiler wanted. Even more, I realized that it forces the right thinking and correct behaviour.

大约一年前,我开始学习Rust。 前两个星期非常痛苦。 什么都没编译,我不知道如何进行基本操作,我无法运行简单的程序。 但是逐步地,我开始理解编译器想要什么。 更重要的是,我意识到这迫使正确的思考和正确的行为。

Yes, sometimes, you have to write seemingly redundant constructs. But it’s better not to compile a correct program than to compile an incorrect one. This makes making mistakes more difficult.

是的,有时候,您必须编写看似多余的结构。 但是,最好不要编译正确的程序,而不要编译不正确的程序。 这使犯错误变得更加困难。

Anyway, soon after, I became more or less productive and finally could do what I wanted. Well, most of the time.

无论如何,不​​久之后,我或多或少地变得有生产力,终于可以做我想要的。 好吧,大多数时候。

Recently out of curiosity, I decided to take on a slightly more complex challenge: implement an HTTP Tunnel in Rust. It turned out to be surprisingly easy to do and took about a day, which is quite impressive. Basically, I stitched together tokio, clap, serde, and several other very useful crates. Okay, enough of the introduction. Let me share the knowledge I gained during this exciting challenge and elaborate on why I organized the app this way. I hope you’ll enjoy it.

最近出于好奇,我决定接受一个稍微复杂的挑战:在Rust中实现HTTP隧道。 事实证明,它非常容易完成,并且花费了大约一天的时间,这非常令人印象深刻。 基本上,我将tokio , clap , serde和其他几个非常有用的板条缝在一起。 好的,足够的介绍。 让我分享在这个激动人心的挑战中获得的知识,并详细说明为什么我以这种方式组织该应用程序。 希望您会喜欢。

什么是HTTP隧道? (What is an HTTP Tunnel?)

Simply put, it’s a lightweight VPN that you can set up with your browser so your Internet provider cannot block or track your activity, and web-servers won’t see your IP address.

简而言之,它是一种轻量级VPN,您可以使用浏览器对其进行设置,以使Internet提供商无法阻止或跟踪您的活动,并且Web服务器不会看到您的IP地址。

If you’d like, you can test it with your browser locally, e.g., with Firefox (otherwise just skip this section for now).

如果需要,可以在本地浏览器(例如,Firefox)上对其进行测试(否则,请暂时跳过此部分)。

  1. Install the app using cargo:

    使用cargo安装应用程序:

$ cargo install http-tunnel

2. Start:

2.开始:

$ http-tunnel --bind 0.0.0.0:8080 http

You can also check the http-tunnel GitHub repository for build/installation instructions.

您还可以检查http-tunnel GitHub存储库以获取构建/安装说明。

Now you can go to your browser and set the HTTP Proxy to localhost:8080. For instance, in Firefox just search for proxy in the preferences section:

现在,您可以转到浏览器并将HTTP Proxy设置为localhost:8080 。 例如,在Firefox中,只需在首选项部分中搜索proxy

and then specify it for HTTP Proxy and also check it for HTTPS:

然后为HTTP Proxy指定它,并为HTTPS:检查它HTTPS:

Setting the proxy to just built `http_tunnel`
将代理设置为刚刚构建的“ http_tunnel”

You can visit several web-pages and check the ./logs/application.log file — all your traffic was going via the tunnel. For example:

您可以访问几个网页并检查./logs/application.log文件-您的所有流量都通过隧道。 例如:

Okay, let’s walk through the process from the beginning.

好的,让我们从头开始。

设计应用 (Design the app)

Each application starts with design, which means we need to define the following:

每个应用程序都从设计开始,这意味着我们需要定义以下内容:

  1. Functional requirements.功能要求。
  2. Non-functional requirements.非功能性要求。
  3. Application abstractions and components.应用程序抽象和组件。

步骤1.功能要求 (Step 1. Functional requirements)

We need to follow the specification outlined here: https://en.wikipedia.org/wiki/HTTP_tunnel :

我们需要遵循此处概述的规范: https : //en.wikipedia.org/wiki/HTTP_tunnel :

Negotiate target with an HTTP CONNECT request. E.g., if the client wants to create a tunnel to www.wikipedia.org, the request will look like:

HTTP CONNECT请求协商目标。 例如,如果客户端要创建到www.wikipedia.org的隧道,则请求将如下所示:

CONNECT www.wikipedia.org:443 HTTP/1.1...

followed by a response, e.g.

随后是回应,例如

HTTP/1.1 200 OK

After this point, just relay TCP traffic both ways until one of the sides closes it, or an I/O error happens.

此后,只需双向中继TCP通信,直到双方之一将其关闭,否则将发生I / O错误。

The HTTP Tunnel should work for both HTTP and HTTPS.

HTTP隧道应同时适用于HTTP和HTTPS。

We also should be able to manage access/block targets (e.g., to block-list trackers).

我们还应该能够管理访问/阻止目标(例如,阻止列表跟踪器)。

步骤2.非功能需求 (Step 2. Non-functional requirements)

The service shouldn’t log any information that identifies users.

该服务不应记录任何可识别用户的信息。

It should have high throughput and low-latency (it should be unnoticeable for users and relatively cheap to run).

它应该具有高吞吐量和低延迟(对于用户来说应该是不明显的,并且运行起来应该相对便宜)。

Ideally, we want it to be resilient to traffic spikes, provide noisy neighbor isolation, and resist basic DDoS attacks.

理想情况下,我们希望它能够应对流量高峰,提供嘈杂的邻居隔离以及抵御基本的DDoS攻击。

Error messaging should be developer-friendly. We want the system to be observable to troubleshoot and tune it in production at a massive scale.

错误消息应该对开发人员友好。 我们希望该系统能够在大规模生产中进行故障排除和调试。

步骤3.组件 (Step 3. Components)

When designing components, we need to first breakdown the app to a set of responsibilities. First, let’s see how our flow diagram looks like:

在设计组件时,我们需要首先将应用分解为一组职责。 首先,让我们看一下流程图的样子:

To implement this, we can introduce the following main components:

为此,我们可以介绍以下主要组件:

  1. TCP/TLS AcceptorTCP / TLS接受器
  2. HTTP CONNECT NegotiatorHTTP CONNECT协商器
  3. Target Connector目标连接器
  4. Full-Duplex Relay全双工中继

实作(Implementation)

TCP / TLS接受器(TCP/TLS Acceptor)

When we roughly know how to organize the app, it’s time to decide which dependencies we should use. For Rust, the best I/O library I know is tokio. In the tokio family, there are many libraries, including tokio-tls, which makes things much simpler. So the TCP acceptor code would look like:

当我们大致了解如何组织应用程序时,就该决定应该使用哪个依赖项了。 对于Rust,我知道的最好的I / O库是tokio 。 在tokio家族中,有许多库,包括tokio-tls ,这使事情变得简单得多。 因此, TCP接受器代码如下所示:

let mut tcp_listener = TcpListener::bind(&proxy_configuration.bind_address).await.map_err(|e| {error!("Error binding address {}: {}",&proxy_configuration.bind_address, e);e})?;

And then the whole acceptor loop + launching asynchronous connection handlers would be:

然后整个接收器循环+启动异步连接处理程序将是:

loop {// Asynchronously wait for an inbound socket.let socket = tcp_listener.accept().await;let dns_resolver_ref = dns_resolver.clone();match socket {Ok((stream, _)) => {let config = config.clone();// handle accepted connections asynchronouslytokio::spawn(async move { tunnel_stream(&config, stream, dns_resolver_ref).await });}Err(e) => error!("Failed TCP handshake {}", e),}}

Let’s break down what’s happening here. We accept a connection. If the operation was successful, use tokio::spawn to create a new task that will handle that connection. Memory/thread-safety management happens behind the scenes. Handling futures is hidden by async/await syntax sugar.

让我们分解一下这里发生的事情。 我们接受连接。 如果操作成功,请使用tokio::spawn创建一个新任务来处理该连接。 内存/线程安全管理在后台进行。 async/await语法糖隐藏了处理期货的方法。

However, there is one question. TcpStream and TlsStream are different objects, but handling both is precisely the same. Can we re-use the same code? In Rust, abstraction is achieved via Traits, which are super handy:

但是,有一个问题。 TcpStreamTlsStream是不同的对象,但是两者的处理完全相同。 我们可以重用相同的代码吗? 在Rust中,通过Traits实现抽象,这非常方便:

/// Tunnel via a client connection.
async fn tunnel_stream<C: AsyncRead + AsyncWrite + Send + Unpin + 'static>(config: &ProxyConfiguration,client_connection: C,dns_resolver: DnsResolver,
) -> io::Result<()> {...}

The stream must implement:

流必须实现:

  • AsyncRead /Write— so we can read/write it asynchronously

    AsyncRead /Write —因此我们可以异步读取/写入它

  • Send— to be able to send between threads

    Send -能够在线程之间发送

  • Unpin— to be moveable (otherwise we won’t be able to do async move and tokio::spawn to create an async task)

    Unpin -可以移动(否则我们将无法执行async movetokio::spawn来创建async任务)

  • 'static —to denote that it may live until application shutdown and doesn’t depend on any other object’s destruction.

    'static -表示它可以一直存在直到应用程序关闭,并且不依赖于任何其他对象的破坏。

Which our TCP/TLS streams exactly are. However, now we can see that it doesn’t have to be TCP/TLS streams. This code would work for UDP or QUIC or ICMP. I.e., it can wrap any protocol within any other protocol, or itself. In other words, this code is reusable, extendable, and ready for migration (which happens sooner or later).

我们的TCP/TLS流到底是哪个。 但是,现在我们可以看到它不必是TCP/TLS流。 此代码适用于UDPQUICICMP 。 即,它可以将任何协议包装在任何其他协议中,或者本身。 换句话说,此代码是可重用,可扩展的,并准备进行迁移(迟早会发生)。

HTTP连接协商器 (HTTP Connect Negotiator)

Let’s pause for a second and think at a higher level. What if we can abstract from HTTP Tunnel, and just need to implement a generic tunnel?

让我们稍停片刻,再思考一下。 如果我们可以从HTTP隧道中抽象出来,而只需要实现一个通用隧道呢?

  1. We need to establish some transport-level connections (L4).我们需要建立一些传输级别的连接(L4)。
  2. Negotiate a target (doesn’t really matter how: HTTP, PPv2, etc.).协商目标(实际上并不重要:HTTP,PPv2等)。
  3. Establish an L4 connection to the target.建立与目标的L4连接。
  4. Report success and start relaying data.报告成功并开始中继数据。

A target could be, for instance, another tunnel. Also, we can support different protocols. The core would stay the same.

目标可以是例如另一个隧道。 此外,我们可以支持不同的协议。 核心将保持不变。

We already saw that tunnel_stream method already works with any L4 Client<->Tunnel connection.

我们已经看到tunnel_stream方法已经适用于任何L4 Client<->Tunnel连接。

#[async_trait]
pub trait TunnelTarget {type Addr;fn addr(&self) -> Self::Addr;
}#[async_trait]
pub trait TargetConnector {type Target: TunnelTarget + Send + Sync + Sized;type Stream: AsyncRead + AsyncWrite + Send + Sized + 'static;async fn connect(&mut self, target: &Self::Target) -> io::Result<Self::Stream>;
}

Here, we specify two abstractions:

在这里,我们指定两个抽象:

  1. TunnelTarget is just something that has an Addr — whatever it is.

    TunnelTarget就是具有Addr东西-无论它是什么。

  2. TargetConnector — can connect to that Addr and needs to return a stream that supports async I/O.

    TargetConnector —可以连接到该Addr并且需要返回支持异步I / O的流。

Okay, but what about the target negotiation? The tokio-utils crate already has an abstraction for that, named Framed streams (with corresponding Encoder/Decoder traits). We need to implement them for HTTP CONNECT (or any other proxy protocol). You can find the implementation here.

好的,但是目标协商呢? tokio-utils板条箱对此已经有了一个抽象,称为Framed流(具有相应的Encoder/Decoder特性)。 我们需要为HTTP CONNECT (或任何其他代理协议)实现它们。 您可以在此处找到实现。

中继 (Relay)

We only have one major component remaining — that which relays data after the tunnel negotiation is done. tokio provides a method to split a stream into two halves: ReadHalf and WriteHalf. We can split both client and target connections and relay them in both directions:

我们只剩下一个主要组件,即在隧道协商完成后中继数据的组件。 tokio提供了一种将流分成两半的方法: ReadHalfWriteHalf 。 我们可以拆分客户端和目标连接,并在两个方向上中继它们:

let (client_recv, client_send) = io::split(client);let (target_recv, target_send) = io::split(target);let upstream_task =tokio::spawn(async move { upstream_relay.relay_data(client_recv, target_send).await});let downstream_task =tokio::spawn(async move { downstream_relay.relay_data(target_recv, client_send).await });

Where the relay_data(…) definition requires nothing more than implementing abstractions mentioned above. I.e., it can connect any two halves of a stream:

其中relay_data(…)定义只需要实现上述抽象即可。 即,它可以连接流的任何两半:

/// Relays data in a single direction. E.g.
pub async fn relay_data<R: AsyncReadExt + Sized, W: AsyncWriteExt + Sized>(self,mut source: ReadHalf<R>,mut dest: WriteHalf<W>,) -> io::Result<RelayStats> {...}

And finally, instead of a simple HTTP Tunnel, we have an engine that can be used to build any type of tunnels or a chain of tunnels (e.g., for onion routing), over any transport and proxy protocols:

最后,除了简单的HTTP隧道,我们还有一个引擎,可用于通过任何传输和代理协议构建任何类型的隧道或隧道链(例如,用于洋葱路由):

/// A connection tunnel.
///
/// # Parameters
/// * `<H>` - proxy handshake codec for initiating a tunnel.
///    It extracts the request message, which contains the target, and, potentially policies.
///    It also takes care of encoding a response.
/// * `<C>` - a connection from from client.
/// * `<T>` - target connector. It takes result produced by the codec and establishes a connection
///           to a target.
///
/// Once the target connection is established, it relays data until any connection is closed or an
/// error happens.
impl<H, C, T> ConnectionTunnel<H, C, T>
whereH: Decoder<Error = EstablishTunnelResult> + Encoder<EstablishTunnelResult>,H::Item: TunnelTarget + Sized + Display + Send + Sync,C: AsyncRead + AsyncWrite + Sized + Send + Unpin + 'static,T: TargetConnector<Target = H::Item>,
{...}

The implementation is almost trivial in basic cases, but we want our app to handle failures, and that’s the focus of the next section.

在基本情况下,实现几乎是微不足道的,但是我们希望我们的应用程序能够处理故障,这是下一部分的重点。

处理失败 (Dealing with failures)

The amount of time engineers deal with failures is proportional to the scale of a system. It’s easy to write happy-case code. Still, if it enters an irrecoverable state on the very first error, it’s painful to use. Besides that, your app will be used by other engineers, and there are very few things more irritating than cryptic/misleading error messages. If your code runs as a part of a large service, some people need to monitor and support it (e.g., SREs or DevOps), and it should be a pleasure for them to deal with your service.

工程师处理故障的时间与系统规模成正比。 编写幸福案例代码很容易。 但是,如果它在第一个错误时进入不可恢复的状态,则使用起来很痛苦。 除此之外,您的应用程序还将由其他工程师使用,并且几乎没有什么比含糊不清/误导性的错误消息更令人讨厌了。 如果您的代码作为大型服务的一部分运行,则有些人需要对其进行监视和支持(例如SRE或DevOps),因此他们应该很高兴处理您的服务。

What kind of failures may an HTTP Tunnel encounter?

HTTP隧道可能遇到什么样的故障?

It’s a good idea to enumerate all error codes that your app returns to the client. So it’s clear why a request failed if the operation can be tried again (or shouldn’t), if it’s an integration bug or just network noise.

枚举应用返回给客户端的所有错误代码是一个好主意。 因此很明显,如果再次尝试该操作(或者不应该尝试该操作),集成错误或仅仅是网络噪音,为什么请求失败。

pub enum EstablishTunnelResult {/// Successfully connected to target.  Ok,/// Malformed requestBadRequest,/// Target is not allowedForbidden,/// Unsupported operation, however valid for the protocol.OperationNotAllowed,/// The client failed to send a tunnel request timely.RequestTimeout,/// Cannot connect to target.BadGateway,/// Connection attempt timed out.GatewayTimeout,/// Busy. Try again later.TooManyRequests,/// Any other error. E.g. an abrupt I/O error.ServerError,
}

Dealing with delays is crucial for a network app. If your operations don’t have timeouts, it’s a matter of time until all of your threads will be Waiting for Godot, or your app will exhaust all available resources and become unavailable. Here we delegate timeout definition to RelayPolicy:

处理延迟对于网络应用程序至关重要。 如果您的操作没有超时,那么所有线程将在等待Waitot还是一个时间问题,否则您的应用程序将耗尽所有可用资源而变得不可用。 在这里,我们将超时定义委托给RelayPolicy

let read_result = self.relay_policy.timed_operation(source.read(&mut buffer)).await;if read_result.is_err() {shutdown_reason = RelayShutdownReasons::ReaderTimeout;break;}let n = match read_result.unwrap() {Ok(n) if n == 0 => {shutdown_reason = RelayShutdownReasons::GracefulShutdown;break;}Ok(n) => n,Err(e) => {error!("{} failed to read. Err = {:?}, CTX={}",self.name, e, self.tunnel_ctx);shutdown_reason = RelayShutdownReasons::ReadError;break;}};

Relay policy can be configured like this:

中继策略可以这样配置:

relay_policy:  idle_timeout: 10s  min_rate_bpm: 1000  max_rate_bps: 10000  max_lifetime: 100smax_total_payload: 100mb

So we can limit activity per connection with max_rate_bps and detecting idle clients with min_rate_bpm (so they don’t consume system resources than can be utilized more productively). A connection lifetime and total traffic may be bounded as well.

因此,我们可以使用max_rate_bps限制每个连接的活动,并使用min_rate_bpm来检测空闲客户端(这样,它们不会消耗系统资源,而无法更有效地利用它们)。 连接寿命和总流量也可能受到限制。

It goes without saying that each failure mode needs to be tested. It’s straightforward to do that in Rust in general and with tokio-test in particular:

不言而喻,每种故障模式都需要进行测试。 通常,在Rust中,特别是在tokio-test中,这样做很简单:

#[tokio::test]async fn test_timed_operation_timeout() {let time_duration = 1;let data = b"data on the wire";let mut mock_connection: Mock = Builder::new().wait(Duration::from_secs(time_duration * 2)).read(data).build();let relay_policy: RelayPolicy = RelayPolicyBuilder::default().min_rate_bpm(1000).max_rate_bps(100_000).idle_timeout(Duration::from_secs(time_duration)).build().unwrap();let mut buf = [0; 1024];let timed_future = relay_policy.timed_operation(mock_connection.read(&mut buf)).await;assert!(timed_future.is_err());}

The same goes for I/O errors:

I / O错误也是如此:

#[tokio::test]async fn test_timed_operation_failed_io() {let mut mock_connection: Mock = Builder::new().read_error(Error::from(ErrorKind::BrokenPipe)).build();let relay_policy: RelayPolicy = RelayPolicyBuilder::default().min_rate_bpm(1000).max_rate_bps(100_000).idle_timeout(Duration::from_secs(5)).build().unwrap();let mut buf = [0; 1024];let timed_future = relay_policy.timed_operation(mock_connection.read(&mut buf)).await;assert!(timed_future.is_ok()); // no timeoutassert!(timed_future.unwrap().is_err()); // but io-error}

记录和指标(Logging and metrics)

I haven’t seen an application that failed only in ways anticipated by its developers. I’m not saying there are no such applications. Still, chances are that your app is going to encounter something you didn’t expect: data races, specific traffic patterns, dealing with traffic bursts, legacy clients.

我还没有看到仅以开发人员预期的方式失败的应用程序。 我并不是说没有这样的应用程序。 尽管如此,您的应用仍有可能遇到意想不到的事情:数据争用,特定的流量模式,处理流量突发,旧客户端。

But probably one of the most common types of failures is human failures, such as pushing bad code or configuration, which are inevitable in large projects. Anyway, we need to be able to deal with something we didn’t foresee. So we emit enough information that would allow us to detect failures and troubleshoot.

但是,最常见的故障类型之一可能是人为故障,例如推送错误的代码或配置,这在大型项目中是不可避免的。 无论如何,我们需要能够处理我们未曾预见的事情。 因此,我们发出了足够的信息,使我们能够检测到故障并进行故障排除。

So we’d better log every error and important events with meaningful information and relevant context as well as statistics.

因此,我们最好使用有意义的信息,相关的上下文以及统计信息来记录每个错误和重要事件。

/// Stats after the relay is closed. Can be used for telemetry/monitoring.
#[derive(Builder, Clone, Debug, Serialize)]
pub struct RelayStats {pub shutdown_reason: RelayShutdownReasons,pub total_bytes: usize,pub event_count: usize,pub duration: Duration,
}/// Statistics. No sensitive information.
#[derive(Serialize)]
pub struct TunnelStats {tunnel_ctx: TunnelCtx,result: EstablishTunnelResult,upstream_stats: Option<RelayStats>,downstream_stats: Option<RelayStats>,
}

Please note the tunnel_ctx: TunnelCtx field, which can be used to correlate metric records with log messages:

请注意tunnel_ctx: TunnelCtx字段,该字段可用于将度量标准记录与日志消息相关联:

error!(    "{} failed to write {} bytes. Err = {:?}, CTX={}",    self.name, n, e, self.tunnel_ctx);

配置和参数 (Configuration and parameters)

Last but not least. We’d like to be able to run our tunnel in different modes with different parameters. Here’s where serde and clap become handy.

最后但并非最不重要的。 我们希望能够在具有不同参数的不同模式下运行隧道。 这是serdeclap变得很方便的地方。

let matches = clap_app!(myapp =>(name: "Simple HTTP(S) Tunnel")(version: "0.1.0")(author: "Eugene Retunsky")(about: "A simple HTTP(S) tunnel")(@arg CONFIG: --config +required +takes_value "Configuration file")(@arg BIND: --bind +required +takes_value "Bind address, e.g. 0.0.0.0:8443")(@subcommand http =>(about: "Run the tunnel in HTTP mode")(version: "0.1.0"))(@subcommand https =>(about: "Run the tunnel in HTTPS mode")(version: "0.1.0")(@arg PKCS12: --pk +required +takes_value "pkcs12 filename")(@arg PASSWORD: --password +required  +takes_value "Password for the pkcs12 file"))).get_matches();

In my opinion, clap makes dealing with command line parameters pleasant. Extraordinarily expressive and easy to maintain.

我认为, clap使处理命令行参数变得愉快。 极富表现力,易于维护。

Configuration files can be easily handled with serde-yaml:

配置文件可以使用serde-yaml轻松处理:

target_connection:  dns_cache_ttl: 60s  allowed_targets: "(?i)(wikipedia|rust-lang)\\.org:443$"  connect_timeout: 10s  relay_policy:    idle_timeout: 10s    min_rate_bpm: 1000    max_rate_bps: 10000

Which just corresponds to Rust structs:

刚对应于Rust结构:

#[derive(Deserialize, Clone)]
pub struct TargetConnectionConfig {#[serde(with = "humantime_serde")]pub dns_cache_ttl: Duration,#[serde(with = "serde_regex")]pub allowed_targets: Regex,#[serde(with = "humantime_serde")]pub connect_timeout: Duration,pub relay_policy: RelayPolicy,
}#[derive(Builder, Deserialize, Clone)]
pub struct RelayPolicy {#[serde(with = "humantime_serde")]pub idle_timeout: Duration,/// Min bytes-per-minute (bpm)pub min_rate_bpm: u64,// Max bytes-per-second (bps)pub max_rate_bps: u64,
}

It doesn’t need any additional comments to make it readable and maintainable, and that is beautiful.

它不需要任何其他注释即可使其可读性和可维护性,这很漂亮。

结论 (Conclusion)

As you could see from this quick overview, the Rust ecosystem already provides many building blocks so you can focus on what you need to do rather than how. You didn’t see any memory/resources management or explicit thread-safety (which often comes at the expense of concurrency). Abstraction mechanisms are fantastic, so your code can be highly reusable. This task was a lot of fun, so I’ll try to take on the next challenge.

正如你可以从这个简要概述看到,锈生态系统已经提供了很多积木这样你就可以专注于你需要做的,而不是怎么。 您没有看到任何内存/资源管理或显式的线程安全性(通常以并发为代价)。 抽象机制非常棒,因此您的代码可以高度重用。 这项任务很有趣,因此我将尝试应对下一个挑战。

翻译自: https://medium.com/@xnuter/writing-a-modern-http-s-tunnel-in-rust-56e70d898700


http://www.taodudu.cc/news/show-3281149.html

相关文章:

  • 一个iptables shell脚本
  • 记一次pptp实践经历
  • 一个人的周末,我在歌唱
  • 为了写个网络互连技术课程设计搞了一个星期ensp
  • ew传输_ew。 好一个星期。 重新发现基础知识。
  • 麒麟V10 设置打印机
  • 打印机设置
  • 计算机打印机设置在哪里,打印机设置在哪里 打印机设置在哪里进行设置
  • 计算机用户打印权限设置,如何设置打印机权限?
  • 计算机配置怎么设置,电脑这么配置打印机_电脑如何设置打印机
  • win32print设置打印机属性进行pdf打印
  • 【文印技巧】设置打印机默认“仅允许黑色墨水”打印
  • python设置打印机参数_打印文件并配置打印机设置
  • C++设置打印机暂停打印SetPrinter
  • 如何用python写程序设置当前打印机为默认打印机_Python使用win32print模块设置打印机...
  • 是为计算机局域网内的用户设置的,电脑中怎么在局域网内设置打印机的共享
  • 计算机打印机端口配置,如何设置打印机端口
  • 怎么把计算机跟打印机ip固定,电脑怎么设置打印机ip地址
  • c#FastReport打印机设置,设置打印机名
  • 微信昵称在数据库存储处理,解决显示乱码方案
  • sqlite:微信数据库
  • Mysql表数据如何导入到微信云开发数据库中
  • 微信小程序云开发入门(二)-数据库详解
  • 微信小程序怎么连接数据库?
  • 微信小程序云数据库实现登录
  • 微信小程序开发---连接云开发数据库,实现数据获取
  • 微信小程序怎么取mysql,微信小程序怎么读取数据库?小程序如何读取数据?
  • 微信小程序 连接云数据库(不使用云函数)进行 登录、注册、查询(包括模糊查询)快速实现 亲测可用
  • 微信小程序访问数据库
  • 微信小程序·云开发 云数据库的使用教程

在锈中写一个现代的http隧道相关推荐

  1. 如何在React Native中写一个自定义模块

    前言 在 React Native 项目中可以看到 node_modules 文件夹,这是存放 node 模块的地方,Node.js 的包管理器 npm 是全球最大的开源库生态系统.提到npm,一般指 ...

  2. 如何在 React Native 中写一个自定义模块

    前言 在 React Native 项目中可以看到 node_modules 文件夹,这是存放 node 模块的地方,Node.js 的包管理器 npm 是全球最大的开源库生态系统.提到npm,一般指 ...

  3. Java IO练习--在程序中写一个“HelloJavaWorld你好世界“输出到操作系统文件Hello.txt文件中

    package com.kj.test;import cn.hutool.core.io.IoUtil;import java.io.File; import java.io.FileOutputSt ...

  4. 在程序中写一个“HelloJavaWorld你好世界“输出到操作系统文件Hello.txt文件中

    import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOExce ...

  5. js中写一个函数,第一秒打印1,第二秒打印2

    js中写一个函数,第一秒打印1,第二秒打印2 1.用let块级作用域 for(let i = 0;i<5;i++){setTimeout(()=>{console.log(i);},100 ...

  6. word中添加java代码怎么写_Java如何在word文档中写一个段落?

    在Java编程中,如何在word文档中写一个段落? 注意:需要访问网址:http://poi.apache.org/download.html , 下载一个Apache POI软件包.这里下载最新版本 ...

  7. html中写一个占内存很大死循环代码,HTML中的循环

    1,首先呢我们要说一下通过标签选择元素 通过标签获取元素 window.onload = function(){ // //获取页面上所有的li // var aLi = document.getEl ...

  8. discuz中写一个表单,数据存入到数据库中,再从数据库读出来显示在列表中

    2019独角兽企业重金招聘Python工程师标准>>> 要做到如下的一个效果: 创建的文件有: ./funds.php ./template/PHPChina/funds/funds ...

  9. 如何在jsp中写一个java方法

    一般用<%!  %>在jsp中写java方法 代码如下: <%@ page language="java" import="java.util.*,ja ...

最新文章

  1. AI矢量绘图软件技能学习视频教程
  2. boost::mpl模块实现sort相关的测试程序
  3. 深入理解JavaScript系列(33):设计模式之策略模式
  4. [转载] c语言中检查命令行参数_C中的命令行参数
  5. java如何将String转换为enum
  6. c语言字符数组的应用编程,C语言基础(一)
  7. usb4java android,USB audio on Android platform
  8. HAProxy + Keepalived实现MySQL的高可用负载均衡
  9. 51单片机——LCD12864
  10. 基于python对B站缓存视频的批量复制,重命名
  11. Flink 可视化开发平台--Streamx部署
  12. eclipse 注销快捷键
  13. 2020年最全最好用的在线文档盘点,建议收藏
  14. 计算机中电容状态表示什么,电容上面是字母代表什么
  15. “科林明伦杯”哈尔滨理工大学第十届程序设计竞赛(同步赛) 点对最大值 dp
  16. Kubernetes亲和性学习笔记
  17. 网页安全证书错误但无法安装证书的解决办法
  18. Java pcm文件与wav文件互转
  19. python 两个一样的字符串用==结果为false
  20. 小程序参数二维码生成

热门文章

  1. 印章仿制工具_ps仿制印章工具怎么使用
  2. 程序员未来会成为非常内卷的职业么?
  3. java+ssm的高考志愿选择辅助系统
  4. 人工智能技术在信息技术教学中的使用
  5. .net html5页面缓存技术,.net缓存技术详解
  6. Centaur启动平台质押,收益高达120%年化
  7. linux的more 查看命令,linux中more命令如何查找
  8. 华为中软国际智造云隆重亮相南京软博会并与江苏龙头企业达成战略合作
  9. 传说中的100句英语可以帮你背7000单词
  10. 各大护肤品牌国内专柜价格大全!!-zz