mysql_async/
lib.rs

1// Copyright (c) 2016 Anatoly Ikorsky
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9//! Tokio based asynchronous MySql client library for The Rust Programming Language.
10//!
11//! # Installation
12//!
13//! The library is hosted on [crates.io](https://crates.io/crates/mysql_async/).
14//!
15//! ```toml
16//! [dependencies]
17//! mysql_async = "<desired version>"
18//! ```
19//!
20//! # Crate Features
21//!
22//! Default feature set is wide – it includes all default [`mysql_common` features][myslqcommonfeatures]
23//! as well as `native-tls`-based TLS support.
24//!
25//! ## List Of Features
26//!
27//! *   `minimal` – enables only necessary features (at the moment the only necessary feature
28//!     is `flate2` backend). Enables:
29//!
30//!     -   `flate2/zlib"
31//!
32//!     **Example:**
33//!
34//!     ```toml
35//!     [dependencies]
36//!     mysql_async = { version = "*", default-features = false, features = ["minimal"]}
37//!     ```
38//!
39//!     **Note:* it is possible to use another `flate2` backend by directly choosing it:
40//!
41//!     ```toml
42//!     [dependencies]
43//!     mysql_async = { version = "*", default-features = false }
44//!     flate2 = { version = "*", default-features = false, features = ["rust_backend"] }
45//!     ```
46//!
47//! *   `default` – enables the following set of crate's and dependencies' features:
48//!
49//!     -   `native-tls-tls`
50//!     -   `flate2/zlib"
51//!     -   `mysql_common/bigdecimal03`
52//!     -   `mysql_common/rust_decimal`
53//!     -   `mysql_common/time03`
54//!     -   `mysql_common/uuid`
55//!     -   `mysql_common/frunk`
56//!
57//! *   `default-rustls` – same as default but with `rustls-tls` instead of `native-tls-tls`.
58//!
59//!     **Example:**
60//!
61//!     ```toml
62//!     [dependencies]
63//!     mysql_async = { version = "*", default-features = false, features = ["default-rustls"] }
64//!     ```
65//!
66//! *   `native-tls-tls` – enables `native-tls`-based TLS support _(conflicts with `rustls-tls`)_
67//!
68//!     **Example:**
69//!
70//!     ```toml
71//!     [dependencies]
72//!     mysql_async = { version = "*", default-features = false, features = ["native-tls-tls"] }
73//!
74//! *   `rustls-tls` – enables `native-tls`-based TLS support _(conflicts with `native-tls-tls`)_
75//!
76//!     **Example:**
77//!
78//!     ```toml
79//!     [dependencies]
80//!     mysql_async = { version = "*", default-features = false, features = ["rustls-tls"] }
81//!
82//! *   `tracing` – enables instrumentation via `tracing` package.
83//!
84//!     Primary operations (`query`, `prepare`, `exec`) are instrumented at `INFO` level.
85//!     Remaining operations, incl. `get_conn`, are instrumented at `DEBUG` level.
86//!     Also at `DEBUG`, the SQL queries and parameters are added to the `query`, `prepare`
87//!     and `exec` spans. Also some internal queries are instrumented at `TRACE` level.
88//!
89//!     **Example:**
90//!
91//!     ```toml
92//!     [dependencies]
93//!     mysql_async = { version = "*", features = ["tracing"] }
94//!     ```
95//!
96//! *   `derive` – enables `mysql_commom/derive` feature
97//!
98//! [myslqcommonfeatures]: https://github.com/blackbeam/rust_mysql_common#crate-features
99//!
100//! # TLS/SSL Support
101//!
102//! SSL support comes in two flavors:
103//!
104//! 1.  Based on native-tls – this is the default option, that usually works without pitfalls
105//!     (see the `native-tls-tls` crate feature).
106//!
107//! 2.  Based on rustls – TLS backend written in Rust (see the `rustls-tls` crate feature).
108//!
109//!     Please also note a few things about rustls:
110//!     -   it will fail if you'll try to connect to the server by its IP address,
111//!         hostname is required;
112//!     -   it, most likely, won't work on windows, at least with default server certs,
113//!         generated by the MySql installer.
114//!
115//! # Connection URL parameters
116//!
117//! There is a set of url-parameters supported by the driver (see documentation on [`Opts`]).
118//!
119//! # Example
120//!
121//! ```rust
122//! # use mysql_async::{Result, test_misc::get_opts};
123//! use mysql_async::prelude::*;
124//! # use std::env;
125//!
126//! #[derive(Debug, PartialEq, Eq, Clone)]
127//! struct Payment {
128//!     customer_id: i32,
129//!     amount: i32,
130//!     account_name: Option<String>,
131//! }
132//!
133//! #[tokio::main]
134//! async fn main() -> Result<()> {
135//!     let payments = vec![
136//!         Payment { customer_id: 1, amount: 2, account_name: None },
137//!         Payment { customer_id: 3, amount: 4, account_name: Some("foo".into()) },
138//!         Payment { customer_id: 5, amount: 6, account_name: None },
139//!         Payment { customer_id: 7, amount: 8, account_name: None },
140//!         Payment { customer_id: 9, amount: 10, account_name: Some("bar".into()) },
141//!     ];
142//!
143//!     let database_url = /* ... */
144//!     # get_opts();
145//!
146//!     let pool = mysql_async::Pool::new(database_url);
147//!     let mut conn = pool.get_conn().await?;
148//!
149//!     // Create a temporary table
150//!     r"CREATE TEMPORARY TABLE payment (
151//!         customer_id int not null,
152//!         amount int not null,
153//!         account_name text
154//!     )".ignore(&mut conn).await?;
155//!
156//!     // Save payments
157//!     r"INSERT INTO payment (customer_id, amount, account_name)
158//!       VALUES (:customer_id, :amount, :account_name)"
159//!         .with(payments.iter().map(|payment| params! {
160//!             "customer_id" => payment.customer_id,
161//!             "amount" => payment.amount,
162//!             "account_name" => payment.account_name.as_ref(),
163//!         }))
164//!         .batch(&mut conn)
165//!         .await?;
166//!
167//!     // Load payments from the database. Type inference will work here.
168//!     let loaded_payments = "SELECT customer_id, amount, account_name FROM payment"
169//!         .with(())
170//!         .map(&mut conn, |(customer_id, amount, account_name)| Payment { customer_id, amount, account_name })
171//!         .await?;
172//!
173//!     // Dropped connection will go to the pool
174//!     drop(conn);
175//!
176//!     // The Pool must be disconnected explicitly because
177//!     // it's an asynchronous operation.
178//!     pool.disconnect().await?;
179//!
180//!     assert_eq!(loaded_payments, payments);
181//!
182//!     // the async fn returns Result, so
183//!     Ok(())
184//! }
185//! ```
186//!
187//! # Pool
188//!
189//! The [`Pool`] structure is an asynchronous connection pool.
190//!
191//! Please note:
192//!
193//! * [`Pool`] is a smart pointer – each clone will point to the same pool instance.
194//! * [`Pool`] is `Send + Sync + 'static` – feel free to pass it around.
195//! * use [`Pool::disconnect`] to gracefuly close the pool.
196//! * ⚠️ [`Pool::new`] is lazy and won't assert server availability.
197//!
198//! # Transaction
199//!
200//! [`Conn::start_transaction`] is a wrapper, that starts with `START TRANSACTION`
201//! and ends with `COMMIT` or `ROLLBACK`.
202//!
203//! Dropped transaction will be implicitly rolled back if it wasn't explicitly
204//! committed or rolled back. Note that this behaviour will be triggered by a pool
205//! (on conn drop) or by the next query, i.e. may be delayed.
206//!
207//! API won't allow you to run nested transactions because some statements causes
208//! an implicit commit (`START TRANSACTION` is one of them), so this behavior
209//! is chosen as less error prone.
210//!
211//! # `Value`
212//!
213//! This enumeration represents the raw value of a MySql cell. Library offers conversion between
214//! `Value` and different rust types via `FromValue` trait described below.
215//!
216//! ## `FromValue` trait
217//!
218//! This trait is reexported from **mysql_common** create. Please refer to its
219//! [crate docs](https://docs.rs/mysql_common) for the list of supported conversions.
220//!
221//! Trait offers conversion in two flavours:
222//!
223//! *   `from_value(Value) -> T` - convenient, but panicking conversion.
224//!
225//!     Note, that for any variant of `Value` there exist a type, that fully covers its domain,
226//!     i.e. for any variant of `Value` there exist `T: FromValue` such that `from_value` will never
227//!     panic. This means, that if your database schema is known, than it's possible to write your
228//!     application using only `from_value` with no fear of runtime panic.
229//!
230//!     Also note, that some convertions may fail even though the type seem sufficient,
231//!     e.g. in case of invalid dates (see [sql mode](https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html)).
232//!
233//! *   `from_value_opt(Value) -> Option<T>` - non-panicking, but less convenient conversion.
234//!
235//!     This function is useful to probe conversion in cases, where source database schema
236//!     is unknown.
237//!
238//! # MySql query protocols
239//!
240//! ## Text protocol
241//!
242//! MySql text protocol is implemented in the set of `Queryable::query*` methods
243//! and in the [`prelude::Query`] trait if query is [`prelude::AsQuery`].
244//! It's useful when your query doesn't have parameters.
245//!
246//! **Note:** All values of a text protocol result set will be encoded as strings by the server,
247//! so `from_value` conversion may lead to additional parsing costs.
248//!
249//! ## Binary protocol and prepared statements.
250//!
251//! MySql binary protocol is implemented in the set of `exec*` methods,
252//! defined on the [`prelude::Queryable`] trait and in the [`prelude::Query`]
253//! trait if query is [`QueryWithParams`]. Prepared statements is the only way to
254//! pass rust value to the MySql server. MySql uses `?` symbol as a parameter placeholder.
255//!
256//! **Note:** it's only possible to use parameters where a single MySql value
257//! is expected, i.e. you can't execute something like `SELECT ... WHERE id IN ?`
258//! with a vector as a parameter. You'll need to build a query that looks like
259//! `SELECT ... WHERE id IN (?, ?, ...)` and to pass each vector element as
260//! a parameter.
261//!
262//! # Named parameters
263//!
264//! MySql itself doesn't have named parameters support, so it's implemented on the client side.
265//! One should use `:name` as a placeholder syntax for a named parameter. Named parameters uses
266//! the following naming convention:
267//!
268//! * parameter name must start with either `_` or `a..z`
269//! * parameter name may continue with `_`, `a..z` and `0..9`
270//!
271//! **Note:** this rules mean that, say, the statment `SELECT :fooBar` will be translated
272//! to `SELECT ?Bar` so please be careful.
273//!
274//! Named parameters may be repeated within the statement, e.g `SELECT :foo, :foo` will require
275//! a single named parameter `foo` that will be repeated on the corresponding positions during
276//! statement execution.
277//!
278//! One should use the `params!` macro to build parameters for execution.
279//!
280//! **Note:** Positional and named parameters can't be mixed within the single statement.
281//!
282//! # Statements
283//!
284//! In MySql each prepared statement belongs to a particular connection and can't be executed
285//! on another connection. Trying to do so will lead to an error. The driver won't tie statement
286//! to its connection in any way, but one can look on to the connection id, contained
287//! in the [`Statement`] structure.
288//!
289//! # LOCAL INFILE Handlers
290//!
291//! **Warning:** You should be aware of [Security Considerations for LOAD DATA LOCAL][1].
292//!
293//! There are two flavors of LOCAL INFILE handlers – _global_ and _local_.
294//!
295//! I case of a LOCAL INFILE request from the server the driver will try to find a handler for it:
296//!
297//! 1.  It'll try to use _local_ handler installed on the connection, if any;
298//! 2.  It'll try to use _global_ handler, specified via [`OptsBuilder::local_infile_handler`],
299//!     if any;
300//! 3.  It will emit [`LocalInfileError::NoHandler`] if no handlers found.
301//!
302//! The purpose of a handler (_local_ or _global_) is to return [`InfileData`].
303//!
304//! ## _Global_ LOCAL INFILE handler
305//!
306//! See [`prelude::GlobalHandler`].
307//!
308//! Simply speaking the _global_ handler is an async function that takes a file name (as `&[u8]`)
309//! and returns `Result<InfileData>`.
310//!
311//! You can set it up using [`OptsBuilder::local_infile_handler`]. Server will use it if there is no
312//! _local_ handler installed for the connection. This handler might be called multiple times.
313//!
314//! Examles:
315//!
316//! 1.  [`WhiteListFsHandler`] is a _global_ handler.
317//! 2.  Every `T: Fn(&[u8]) -> BoxFuture<'static, Result<InfileData, LocalInfileError>>`
318//!     is a _global_ handler.
319//!
320//! ## _Local_ LOCAL INFILE handler.
321//!
322//! Simply speaking the _local_ handler is a future, that returns `Result<InfileData>`.
323//!
324//! This is a one-time handler – it's consumed after use. You can set it up using
325//! [`Conn::set_infile_handler`]. This handler have priority over _global_ handler.
326//!
327//! Worth noting:
328//!
329//! 1.  `impl Drop for Conn` will clear _local_ handler, i.e. handler will be removed when
330//!     connection is returned to a `Pool`.
331//! 2.  [`Conn::reset`] will clear _local_ handler.
332//!
333//! Example:
334//!
335//! ```rust
336//! # use mysql_async::{prelude::*, test_misc::get_opts, OptsBuilder, Result, Error};
337//! # use futures_util::future::FutureExt;
338//! # use futures_util::stream::{self, StreamExt};
339//! # use bytes::Bytes;
340//! # use std::env;
341//! # #[tokio::main]
342//! # async fn main() -> Result<()> {
343//! #
344//! # let database_url = get_opts();
345//! let pool = mysql_async::Pool::new(database_url);
346//!
347//! let mut conn = pool.get_conn().await?;
348//! "CREATE TEMPORARY TABLE tmp (id INT, val TEXT)".ignore(&mut conn).await?;
349//!
350//! // We are going to call `LOAD DATA LOCAL` so let's setup a one-time handler.
351//! conn.set_infile_handler(async move {
352//!     // We need to return a stream of `io::Result<Bytes>`
353//!     Ok(stream::iter([Bytes::from("1,a\r\n"), Bytes::from("2,b\r\n3,c")]).map(Ok).boxed())
354//! });
355//!
356//! let result = r#"LOAD DATA LOCAL INFILE 'whatever'
357//!     INTO TABLE `tmp`
358//!     FIELDS TERMINATED BY ',' ENCLOSED BY '\"'
359//!     LINES TERMINATED BY '\r\n'"#.ignore(&mut conn).await;
360//!
361//! match result {
362//!     Ok(()) => (),
363//!     Err(Error::Server(ref err)) if err.code == 1148 => {
364//!         // The used command is not allowed with this MySQL version
365//!         return Ok(());
366//!     },
367//!     Err(Error::Server(ref err)) if err.code == 3948 => {
368//!         // Loading local data is disabled;
369//!         // this must be enabled on both the client and the server
370//!         return Ok(());
371//!     }
372//!     e @ Err(_) => e.unwrap(),
373//! }
374//!
375//! // Now let's verify the result
376//! let result: Vec<(u32, String)> = conn.query("SELECT * FROM tmp ORDER BY id ASC").await?;
377//! assert_eq!(
378//!     result,
379//!     vec![(1, "a".into()), (2, "b".into()), (3, "c".into())]
380//! );
381//!
382//! drop(conn);
383//! pool.disconnect().await?;
384//! # Ok(())
385//! # }
386//! ```
387//!
388//! [1]: https://dev.mysql.com/doc/refman/8.0/en/load-data-local-security.html
389//!
390//! # Testing
391//!
392//! Tests uses followin environment variables:
393//! * `DATABASE_URL` – defaults to `mysql://root:password@127.0.0.1:3307/mysql`
394//! * `COMPRESS` – set to `1` or `true` to enable compression for tests
395//! * `SSL` – set to `1` or `true` to enable TLS for tests
396//!
397//! You can run a test server using doker. Please note that params related
398//! to max allowed packet, local-infile and binary logging are required
399//! to properly run tests (please refer to `azure-pipelines.yml`):
400//!
401//! ```sh
402//! docker run -d --name container \
403//!     -v `pwd`:/root \
404//!     -p 3307:3306 \
405//!     -e MYSQL_ROOT_PASSWORD=password \
406//!     mysql:8.0 \
407//!     --max-allowed-packet=36700160 \
408//!     --local-infile \
409//!     --log-bin=mysql-bin \
410//!     --log-slave-updates \
411//!     --gtid_mode=ON \
412//!     --enforce_gtid_consistency=ON \
413//!     --server-id=1
414//! ```
415//!
416
417#![recursion_limit = "1024"]
418#![cfg_attr(feature = "nightly", feature(test))]
419
420#[cfg(feature = "nightly")]
421extern crate test;
422
423#[cfg(feature = "derive")]
424extern crate mysql_common;
425
426pub use mysql_common::{constants as consts, params};
427
428use std::sync::Arc;
429
430mod buffer_pool;
431
432#[macro_use]
433mod tracing_utils;
434
435#[macro_use]
436mod macros;
437mod conn;
438mod connection_like;
439/// Errors used in this crate
440mod error;
441mod io;
442mod local_infile_handler;
443mod opts;
444mod query;
445mod queryable;
446
447type BoxFuture<'a, T> = futures_core::future::BoxFuture<'a, Result<T>>;
448
449static BUFFER_POOL: once_cell::sync::Lazy<Arc<crate::buffer_pool::BufferPool>> =
450    once_cell::sync::Lazy::new(|| Default::default());
451
452#[doc(inline)]
453pub use self::conn::{binlog_stream::BinlogStream, Conn};
454
455#[doc(inline)]
456pub use self::conn::pool::Pool;
457
458#[doc(inline)]
459pub use self::error::{
460    DriverError, Error, IoError, LocalInfileError, ParseError, Result, ServerError, UrlError,
461};
462
463#[doc(inline)]
464pub use self::query::QueryWithParams;
465
466#[doc(inline)]
467pub use self::queryable::transaction::IsolationLevel;
468
469#[doc(inline)]
470#[cfg(any(feature = "rustls", feature = "native-tls"))]
471pub use self::opts::ClientIdentity;
472
473#[doc(inline)]
474pub use self::opts::{
475    ChangeUserOpts, Opts, OptsBuilder, PoolConstraints, PoolOpts, SslOpts,
476    DEFAULT_INACTIVE_CONNECTION_TTL, DEFAULT_POOL_CONSTRAINTS, DEFAULT_STMT_CACHE_SIZE,
477    DEFAULT_TTL_CHECK_INTERVAL,
478};
479
480#[doc(inline)]
481pub use self::local_infile_handler::{builtin::WhiteListFsHandler, InfileData};
482
483#[doc(inline)]
484pub use mysql_common::packets::{
485    binlog_request::BinlogRequest,
486    session_state_change::{
487        Gtids, Schema, SessionStateChange, SystemVariable, TransactionCharacteristics,
488        TransactionState, Unsupported,
489    },
490    BinlogDumpFlags, Column, GnoInterval, OkPacket, SessionStateInfo, Sid,
491};
492
493pub mod binlog {
494    #[doc(inline)]
495    pub use mysql_common::binlog::consts::*;
496
497    #[doc(inline)]
498    pub use mysql_common::binlog::{events, jsonb, jsondiff, row, value};
499}
500
501#[doc(inline)]
502pub use mysql_common::proto::codec::Compression;
503
504#[doc(inline)]
505pub use mysql_common::row::Row;
506
507#[doc(inline)]
508pub use mysql_common::params::Params;
509
510#[doc(inline)]
511pub use mysql_common::value::Value;
512
513#[doc(inline)]
514pub use mysql_common::row::convert::{from_row, from_row_opt, FromRowError};
515
516#[doc(inline)]
517pub use mysql_common::value::convert::{from_value, from_value_opt, FromValueError};
518
519#[doc(inline)]
520pub use mysql_common::value::json::{Deserialized, Serialized};
521
522#[doc(inline)]
523pub use self::queryable::query_result::{result_set_stream::ResultSetStream, QueryResult};
524
525#[doc(inline)]
526pub use self::queryable::transaction::{Transaction, TxOpts};
527
528#[doc(inline)]
529pub use self::queryable::{BinaryProtocol, TextProtocol};
530
531#[doc(inline)]
532pub use self::queryable::stmt::Statement;
533
534/// Futures used in this crate
535pub mod futures {
536    pub use crate::conn::pool::futures::{DisconnectPool, GetConn};
537}
538
539/// Traits used in this crate
540pub mod prelude {
541    #[doc(inline)]
542    pub use crate::local_infile_handler::GlobalHandler;
543    #[doc(inline)]
544    pub use crate::query::AsQuery;
545    #[doc(inline)]
546    pub use crate::query::{BatchQuery, Query, WithParams};
547    #[doc(inline)]
548    pub use crate::queryable::Queryable;
549    #[doc(inline)]
550    pub use mysql_common::prelude::FromRow;
551    #[doc(inline)]
552    pub use mysql_common::prelude::{FromValue, ToValue};
553
554    /// Everything that is a statement.
555    ///
556    /// ```no_run
557    /// # use std::{borrow::Cow, sync::Arc};
558    /// # use mysql_async::{Statement, prelude::StatementLike};
559    /// fn type_is_a_stmt<T: StatementLike>() {}
560    ///
561    /// type_is_a_stmt::<Cow<'_, str>>();
562    /// type_is_a_stmt::<&'_ str>();
563    /// type_is_a_stmt::<String>();
564    /// type_is_a_stmt::<Box<str>>();
565    /// type_is_a_stmt::<Arc<str>>();
566    /// type_is_a_stmt::<Statement>();
567    ///
568    /// fn ref_to_a_clonable_stmt_is_also_a_stmt<T: StatementLike + Clone>() {
569    ///     type_is_a_stmt::<&T>();
570    /// }
571    /// ```
572    pub trait StatementLike: crate::queryable::stmt::StatementLike {}
573    impl<T: crate::queryable::stmt::StatementLike> StatementLike for T {}
574
575    /// Everything that is a connection.
576    ///
577    /// Note that you could obtain a `'static` connection by giving away `Conn` or `Pool`.
578    pub trait ToConnection<'a, 't: 'a>: crate::connection_like::ToConnection<'a, 't> {}
579    // explicitly implemented because of rusdoc
580    impl<'a> ToConnection<'a, 'static> for &'a crate::Pool {}
581    impl<'a> ToConnection<'static, 'static> for crate::Pool {}
582    impl ToConnection<'static, 'static> for crate::Conn {}
583    impl<'a> ToConnection<'a, 'static> for &'a mut crate::Conn {}
584    impl<'a, 't> ToConnection<'a, 't> for &'a mut crate::Transaction<'t> {}
585
586    /// Trait for protocol markers [`crate::TextProtocol`] and [`crate::BinaryProtocol`].
587    pub trait Protocol: crate::queryable::Protocol {}
588    impl Protocol for crate::BinaryProtocol {}
589    impl Protocol for crate::TextProtocol {}
590
591    pub use mysql_common::params;
592}
593
594#[doc(hidden)]
595pub mod test_misc {
596    use lazy_static::lazy_static;
597
598    use std::env;
599
600    use crate::opts::{Opts, OptsBuilder, SslOpts};
601
602    #[allow(dead_code)]
603    #[allow(unreachable_code)]
604    fn error_should_implement_send_and_sync() {
605        fn _dummy<T: Send + Sync + Unpin>(_: T) {}
606        _dummy(panic!());
607    }
608
609    lazy_static! {
610        pub static ref DATABASE_URL: String = {
611            if let Ok(url) = env::var("DATABASE_URL") {
612                let opts = Opts::from_url(&url).expect("DATABASE_URL invalid");
613                if opts
614                    .db_name()
615                    .expect("a database name is required")
616                    .is_empty()
617                {
618                    panic!("database name is empty");
619                }
620                url
621            } else {
622                "mysql://root:password@localhost:3307/mysql".into()
623            }
624        };
625    }
626
627    pub fn get_opts() -> OptsBuilder {
628        let mut builder = OptsBuilder::from_opts(Opts::from_url(&**DATABASE_URL).unwrap());
629        if test_ssl() {
630            let ssl_opts = SslOpts::default()
631                .with_danger_skip_domain_validation(true)
632                .with_danger_accept_invalid_certs(true);
633            builder = builder.prefer_socket(false).ssl_opts(ssl_opts);
634        }
635        if test_compression() {
636            builder = builder.compression(crate::Compression::default());
637        }
638        builder
639    }
640
641    pub fn test_compression() -> bool {
642        ["true", "1"].contains(&&*env::var("COMPRESS").unwrap_or_default())
643    }
644
645    pub fn test_ssl() -> bool {
646        ["true", "1"].contains(&&*env::var("SSL").unwrap_or_default())
647    }
648}