// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. library dart._http; import 'dart:_internal' show checkNotNullable, Since, valueOfNonNullableParamWithDefault, HttpStatus; import 'dart:async' as dart_async show runZoned; import 'dart:async'; import 'dart:collection' show HashMap, HashSet, ListQueue, LinkedList, LinkedListEntry, UnmodifiableMapView; import 'dart:convert'; import 'dart:developer' hide log; import 'dart:io'; import 'dart:isolate' show Isolate; import 'dart:math'; import 'dart:typed_data'; part 'crypto.dart'; part 'embedder_config.dart'; part 'http_date.dart'; part 'http_headers.dart'; part 'http_impl.dart'; part 'http_parser.dart'; part 'http_session.dart'; part 'http_testing.dart'; part 'overrides.dart'; part 'websocket.dart'; part 'websocket_impl.dart'; /// A server that delivers content, such as web pages, using the HTTP protocol. /// /// Note: [HttpServer] provides low-level HTTP functionality. /// We recommend users evaluate the high-level APIs discussed at /// [Write HTTP servers](https://dart.dev/tutorials/server/httpserver) on /// [dart.dev](https://dart.dev/). /// /// `HttpServer` is a [Stream] that provides [HttpRequest] objects. Each /// `HttpRequest` has an associated [HttpResponse] object. /// The server responds to a request by writing to that [HttpResponse] object. /// The following example shows how to bind an `HttpServer` to an IPv6 /// [InternetAddress] on port 80 (the standard port for HTTP servers) /// and how to listen for requests. /// Port 80 is the default HTTP port. However, on most systems accessing /// this requires super-user privileges. For local testing consider /// using a non-reserved port (1024 and above). /// /// ```dart /// import 'dart:io'; /// /// void main() async { /// var server = await HttpServer.bind(InternetAddress.anyIPv6, 80); /// await server.forEach((HttpRequest request) { /// request.response.write('Hello, world!'); /// request.response.close(); /// }); /// } /// ``` /// /// Incomplete requests, in which all or part of the header is missing, are /// ignored, and no exceptions or [HttpRequest] objects are generated for them. /// Likewise, when writing to an [HttpResponse], any [Socket] exceptions are /// ignored and any future writes are ignored. /// /// The [HttpRequest] exposes the request headers and provides the request body, /// if it exists, as a Stream of data. If the body is unread, it is drained /// when the server writes to the HttpResponse or closes it. /// /// ## Bind with a secure HTTPS connection /// /// Use [bindSecure] to create an HTTPS server. /// /// The server presents a certificate to the client. The certificate /// chain and the private key are set in the [SecurityContext] /// object that is passed to [bindSecure]. /// /// ```dart /// import 'dart:io'; /// /// void main() async { /// var chain = /// Platform.script.resolve('certificates/server_chain.pem').toFilePath(); /// var key = Platform.script.resolve('certificates/server_key.pem').toFilePath(); /// var context = SecurityContext() /// ..useCertificateChain(chain) /// ..usePrivateKey(key, password: 'dartdart'); /// var server = /// await HttpServer.bindSecure(InternetAddress.anyIPv6, 443, context); /// await server.forEach((HttpRequest request) { /// request.response.write('Hello, world!'); /// request.response.close(); /// }); /// } /// ``` /// /// The certificates and keys are PEM files, which can be created and /// managed with the tools in OpenSSL. abstract class HttpServer implements Stream { /// Gets and sets the default value of the `Server` header for all responses /// generated by this [HttpServer]. /// /// If [serverHeader] is `null`, no `Server` header will be added to each /// response. /// /// The default value is `null`. String? serverHeader; /// Default set of headers added to all response objects. /// /// By default the following headers are in this set: /// /// Content-Type: text/plain; charset=utf-8 /// X-Frame-Options: SAMEORIGIN /// X-Content-Type-Options: nosniff /// X-XSS-Protection: 1; mode=block /// /// If the `Server` header is added here and the `serverHeader` is set as /// well then the value of `serverHeader` takes precedence. HttpHeaders get defaultResponseHeaders; /// Whether the [HttpServer] should compress the content, if possible. /// /// The content can only be compressed when the response is using /// chunked Transfer-Encoding and the incoming request has `gzip` /// as an accepted encoding in the Accept-Encoding header. /// /// The default value is `false` (compression disabled). /// To enable, set `autoCompress` to `true`. bool autoCompress = false; /// Gets or sets the timeout used for idle keep-alive connections. If no /// further request is seen within [idleTimeout] after the previous request was /// completed, the connection is dropped. /// /// Default is 120 seconds. /// /// Note that it may take up to `2 * idleTimeout` before a idle connection is /// aborted. /// /// To disable, set [idleTimeout] to `null`. Duration? idleTimeout = const Duration(seconds: 120); /// Starts listening for HTTP requests on the specified [address] and /// [port]. /// /// The [address] can either be a [String] or an /// [InternetAddress]. If [address] is a [String], [bind] will /// perform a [InternetAddress.lookup] and use the first value in the /// list. To listen on the loopback adapter, which will allow only /// incoming connections from the local host, use the value /// [InternetAddress.loopbackIPv4] or /// [InternetAddress.loopbackIPv6]. To allow for incoming /// connection from the network use either one of the values /// [InternetAddress.anyIPv4] or [InternetAddress.anyIPv6] to /// bind to all interfaces or the IP address of a specific interface. /// /// If an IP version 6 (IPv6) address is used, both IP version 6 /// (IPv6) and version 4 (IPv4) connections will be accepted. To /// restrict this to version 6 (IPv6) only, use [v6Only] to set /// version 6 only. However, if the address is /// [InternetAddress.loopbackIPv6], only IP version 6 (IPv6) connections /// will be accepted. /// /// If [port] has the value 0 an ephemeral port will be chosen by /// the system. The actual port used can be retrieved using the /// [port] getter. /// /// The optional argument [backlog] can be used to specify the listen /// backlog for the underlying OS listen setup. If [backlog] has the /// value of 0 (the default) a reasonable value will be chosen by /// the system. /// /// The optional argument [shared] specifies whether additional `HttpServer` /// objects can bind to the same combination of `address`, `port` and `v6Only`. /// If `shared` is `true` and more `HttpServer`s from this isolate or other /// isolates are bound to the port, then the incoming connections will be /// distributed among all the bound `HttpServer`s. Connections can be /// distributed over multiple isolates this way. static Future bind( address, int port, { int backlog = 0, bool v6Only = false, bool shared = false, }) => _HttpServer.bind(address, port, backlog, v6Only, shared); /// The [address] can either be a [String] or an /// [InternetAddress]. If [address] is a [String], [bind] will /// perform a [InternetAddress.lookup] and use the first value in the /// list. To listen on the loopback adapter, which will allow only /// incoming connections from the local host, use the value /// [InternetAddress.loopbackIPv4] or /// [InternetAddress.loopbackIPv6]. To allow for incoming /// connection from the network use either one of the values /// [InternetAddress.anyIPv4] or [InternetAddress.anyIPv6] to /// bind to all interfaces or the IP address of a specific interface. /// /// If an IP version 6 (IPv6) address is used, both IP version 6 /// (IPv6) and version 4 (IPv4) connections will be accepted. To /// restrict this to version 6 (IPv6) only, use [v6Only] to set /// version 6 only. /// /// If [port] has the value 0 an ephemeral port will be chosen by /// the system. The actual port used can be retrieved using the /// [port] getter. /// /// The optional argument [backlog] can be used to specify the listen /// backlog for the underlying OS listen setup. If [backlog] has the /// value of 0 (the default) a reasonable value will be chosen by /// the system. /// /// If [requestClientCertificate] is true, the server will /// request clients to authenticate with a client certificate. /// The server will advertise the names of trusted issuers of client /// certificates, getting them from a [SecurityContext], where they have been /// set using [SecurityContext.setClientAuthorities]. /// /// The optional argument [shared] specifies whether additional `HttpServer` /// objects can bind to the same combination of `address`, `port` and `v6Only`. /// If `shared` is `true` and more `HttpServer`s from this isolate or other /// isolates are bound to the port, then the incoming connections will be /// distributed among all the bound `HttpServer`s. Connections can be /// distributed over multiple isolates this way. static Future bindSecure( address, int port, SecurityContext context, { int backlog = 0, bool v6Only = false, bool requestClientCertificate = false, bool shared = false, }) => _HttpServer.bindSecure( address, port, context, backlog, v6Only, requestClientCertificate, shared, ); /// Attaches the HTTP server to an existing [ServerSocket]. When the /// [HttpServer] is closed, the [HttpServer] will just detach itself, /// closing current connections but not closing [serverSocket]. factory HttpServer.listenOn(ServerSocket serverSocket) => _HttpServer.listenOn(serverSocket); /// Permanently stops this [HttpServer] from listening for new /// connections. This closes the [Stream] of [HttpRequest]s with a /// done event. The returned future completes when the server is /// stopped. For a server started using [bind] or [bindSecure] this /// means that the port listened on no longer in use. /// /// If [force] is `true`, active connections will be closed immediately. Future close({bool force = false}); /// The port that the server is listening on. /// /// This is the actual port used when a port of zero is /// specified in the [bind] or [bindSecure] call. int get port; /// The address that the server is listening on. /// /// This is the actual address used when the original address /// was specified as a hostname. InternetAddress get address; /// Sets the timeout, in seconds, for sessions of this [HttpServer]. /// /// The default timeout is 20 minutes. set sessionTimeout(int timeout); /// An [HttpConnectionsInfo] object summarizing the number of /// current connections handled by the server. HttpConnectionsInfo connectionsInfo(); } /// Summary statistics about an [HttpServer]s current socket connections. class HttpConnectionsInfo { /// Total number of socket connections. int total = 0; /// Number of active connections where actual request/response /// processing is active. int active = 0; /// Number of idle connections held by clients as persistent connections. int idle = 0; /// Number of connections which are preparing to close. /// /// Note: These connections are also part of the [active] count as they might /// still be sending data to the client before finally closing. int closing = 0; } /// Headers for HTTP requests and responses. /// /// In some situations, headers are immutable: /// /// * [HttpRequest] and [HttpClientResponse] always have immutable headers. /// /// * [HttpResponse] and [HttpClientRequest] have immutable headers /// from the moment the body is written to. /// /// In these situations, the mutating methods throw exceptions. /// /// For all operations on HTTP headers the header name is /// case-insensitive. /// /// To set the value of a header use the `set()` method: /// /// request.headers.set(HttpHeaders.cacheControlHeader, /// 'max-age=3600, must-revalidate'); /// /// To retrieve the value of a header use the `value()` method: /// /// print(request.headers.value(HttpHeaders.userAgentHeader)); /// /// An `HttpHeaders` object holds a list of values for each name /// as the standard allows. In most cases a name holds only a single value, /// The most common mode of operation is to use `set()` for setting a value, /// and `value()` for retrieving a value. abstract interface class HttpHeaders { static const acceptHeader = "accept"; static const acceptCharsetHeader = "accept-charset"; static const acceptEncodingHeader = "accept-encoding"; static const acceptLanguageHeader = "accept-language"; static const acceptRangesHeader = "accept-ranges"; @Since("2.14") static const accessControlAllowCredentialsHeader = 'access-control-allow-credentials'; @Since("2.14") static const accessControlAllowHeadersHeader = 'access-control-allow-headers'; @Since("2.14") static const accessControlAllowMethodsHeader = 'access-control-allow-methods'; @Since("2.14") static const accessControlAllowOriginHeader = 'access-control-allow-origin'; @Since("2.14") static const accessControlExposeHeadersHeader = 'access-control-expose-headers'; @Since("2.14") static const accessControlMaxAgeHeader = 'access-control-max-age'; @Since("2.14") static const accessControlRequestHeadersHeader = 'access-control-request-headers'; @Since("2.14") static const accessControlRequestMethodHeader = 'access-control-request-method'; static const ageHeader = "age"; static const allowHeader = "allow"; static const authorizationHeader = "authorization"; static const cacheControlHeader = "cache-control"; static const connectionHeader = "connection"; static const contentEncodingHeader = "content-encoding"; static const contentLanguageHeader = "content-language"; static const contentLengthHeader = "content-length"; static const contentLocationHeader = "content-location"; static const contentMD5Header = "content-md5"; static const contentRangeHeader = "content-range"; static const contentTypeHeader = "content-type"; static const dateHeader = "date"; static const etagHeader = "etag"; static const expectHeader = "expect"; static const expiresHeader = "expires"; static const fromHeader = "from"; static const hostHeader = "host"; static const ifMatchHeader = "if-match"; static const ifModifiedSinceHeader = "if-modified-since"; static const ifNoneMatchHeader = "if-none-match"; static const ifRangeHeader = "if-range"; static const ifUnmodifiedSinceHeader = "if-unmodified-since"; static const lastModifiedHeader = "last-modified"; static const locationHeader = "location"; static const maxForwardsHeader = "max-forwards"; static const pragmaHeader = "pragma"; static const proxyAuthenticateHeader = "proxy-authenticate"; static const proxyAuthorizationHeader = "proxy-authorization"; static const rangeHeader = "range"; static const refererHeader = "referer"; static const retryAfterHeader = "retry-after"; static const serverHeader = "server"; static const teHeader = "te"; static const trailerHeader = "trailer"; static const transferEncodingHeader = "transfer-encoding"; static const upgradeHeader = "upgrade"; static const userAgentHeader = "user-agent"; static const varyHeader = "vary"; static const viaHeader = "via"; static const warningHeader = "warning"; static const wwwAuthenticateHeader = "www-authenticate"; static const contentDisposition = "content-disposition"; // Cookie headers from RFC 6265. static const cookieHeader = "cookie"; static const setCookieHeader = "set-cookie"; // TODO(39783): Document this. static const generalHeaders = [ cacheControlHeader, connectionHeader, dateHeader, pragmaHeader, trailerHeader, transferEncodingHeader, upgradeHeader, viaHeader, warningHeader, ]; static const entityHeaders = [ allowHeader, contentEncodingHeader, contentLanguageHeader, contentLengthHeader, contentLocationHeader, contentMD5Header, contentRangeHeader, contentTypeHeader, expiresHeader, lastModifiedHeader, ]; static const responseHeaders = [ acceptRangesHeader, ageHeader, etagHeader, locationHeader, proxyAuthenticateHeader, retryAfterHeader, serverHeader, varyHeader, wwwAuthenticateHeader, contentDisposition, ]; static const requestHeaders = [ acceptHeader, acceptCharsetHeader, acceptEncodingHeader, acceptLanguageHeader, authorizationHeader, expectHeader, fromHeader, hostHeader, ifMatchHeader, ifModifiedSinceHeader, ifNoneMatchHeader, ifRangeHeader, ifUnmodifiedSinceHeader, maxForwardsHeader, proxyAuthorizationHeader, rangeHeader, refererHeader, teHeader, userAgentHeader, ]; /// The date specified by the [dateHeader] header, if any. DateTime? date; /// The date and time specified by the [expiresHeader] header, if any. DateTime? expires; /// The date and time specified by the [ifModifiedSinceHeader] header, if any. DateTime? ifModifiedSince; /// The value of the [hostHeader] header, if any. String? host; /// The value of the port part of the [hostHeader] header, if any. int? port; /// The [ContentType] of the [contentTypeHeader] header, if any. ContentType? contentType; /// The value of the [contentLengthHeader] header, if any. /// /// The value is negative if there is no content length set. int contentLength = -1; /// Whether the connection is persistent (keep-alive). late bool persistentConnection; /// Whether the connection uses chunked transfer encoding. /// /// Reflects and modifies the value of the [transferEncodingHeader] header. late bool chunkedTransferEncoding; /// The values for the header named [name]. /// /// Returns null if there is no header with the provided name, /// otherwise returns a new list containing the current values. /// Not that modifying the list does not change the header. List? operator [](String name); /// Convenience method for the value for a single valued header. /// /// The value must not have more than one value. /// /// Returns `null` if there is no header with the provided name. String? value(String name); /// Adds a header value. /// /// The header named [name] will have a string value derived from [value] /// added to its list of values. /// /// Some headers are single valued, and for these, adding a value will /// replace a previous value. If the [value] is a [DateTime], an /// HTTP date format will be applied. If the value is an [Iterable], /// each element will be added separately. For all other /// types the default [Object.toString] method will be used. /// /// Header names are converted to lower-case unless /// [preserveHeaderCase] is set to true. If two header names are /// the same when converted to lower-case, they are considered to be /// the same header, with one set of values. /// /// The current case of the a header name is that of the name used by /// the last [set] or [add] call for that header. void add(String name, Object value, {bool preserveHeaderCase = false}); /// Sets the header [name] to [value]. /// /// Removes all existing values for the header named [name] and /// then [add]s [value] to it. void set(String name, Object value, {bool preserveHeaderCase = false}); /// Removes a specific value for a header name. /// /// Some headers have system supplied values which cannot be removed. /// For all other headers and values, the [value] is converted to a string /// in the same way as for [add], then that string value is removed from the /// current values of [name]. /// If there are no remaining values for [name], the header is no longer /// considered present. void remove(String name, Object value); /// Removes all values for the specified header name. /// /// Some headers have system supplied values which cannot be removed. /// All other values for [name] are removed. /// If there are no remaining values for [name], the header is no longer /// considered present. void removeAll(String name); /// Performs the [action] on each header. /// /// The [action] function is called with each header's name and a list /// of the header's values. The casing of the name string is determined by /// the last [add] or [set] operation for that particular header, /// which defaults to lower-casing the header name unless explicitly /// set to preserve the case. void forEach(void Function(String name, List values) action); /// Disables folding for the header named [name] when sending the HTTP header. /// /// By default, multiple header values are folded into a /// single header line by separating the values with commas. /// /// The 'set-cookie' header has folding disabled by default. void noFolding(String name); /// Removes all headers. /// /// Some headers have system supplied values which cannot be removed. /// All other header values are removed, and header names with not /// remaining values are no longer considered present. void clear(); } /// Representation of a header value in the form: /// ```plaintext /// value; parameter1=value1; parameter2=value2 /// ``` /// /// [HeaderValue] can be used to conveniently build and parse header /// values on this form. /// /// Parameter values can be omitted, in which case the value is parsed as `null`. /// Values can be doubled quoted to allow characters outside of the RFC 7230 /// token characters and backslash sequences can be used to represent the double /// quote and backslash characters themselves. /// /// To build an "accepts" header with the value /// /// text/plain; q=0.3, text/html /// /// use code like this: /// /// HttpClientRequest request = ...; /// var v = HeaderValue("text/plain", {"q": "0.3"}); /// request.headers.add(HttpHeaders.acceptHeader, v); /// request.headers.add(HttpHeaders.acceptHeader, "text/html"); /// /// To parse the header values use the [parse] static method. /// /// HttpRequest request = ...; /// List values = request.headers[HttpHeaders.acceptHeader]; /// values.forEach((value) { /// HeaderValue v = HeaderValue.parse(value); /// // Use v.value and v.parameters /// }); /// /// An instance of [HeaderValue] is immutable. abstract interface class HeaderValue { /// Creates a new header value object setting the value and parameters. factory HeaderValue([ String value = "", Map parameters = const {}, ]) => _HeaderValue(value, parameters); /// Creates a new header value object from parsing a header value /// string with both value and optional parameters. static HeaderValue parse( String value, { String parameterSeparator = ";", String? valueSeparator, bool preserveBackslash = false, }) { return _HeaderValue.parse( value, parameterSeparator: parameterSeparator.codeUnitAt(0), valueSeparator: valueSeparator?.codeUnitAt(0) ?? _CharCode.NONE, preserveBackslash: preserveBackslash, ); } /// The value of the header. String get value; /// A map of parameters. /// /// This map cannot be modified. Map get parameters; /// Returns the formatted string representation in the form: /// ```plaintext /// value; parameter1=value1; parameter2=value2 /// ``` String toString(); } /// The [session][HttpRequest.session] of an [HttpRequest]. /// /// Stores arbitrary information about a browser session on the server. This /// information is stored in memory so it will be lost when the server exits. /// /// A cookie named "DARTSESSID" is stored on the browser to associate the /// `HttpSession` with a particular client. /// /// ```dart /// import 'dart:io'; /// /// void main() async { /// HttpServer.bind("localhost", 8080).then((server) { /// server.listen((request) { /// final session = request.session; /// if (session.isNew) { /// session["cart"] = []; /// } /// if (request.uri.queryParameters['buy'] != null) { /// final item = request.uri.queryParameters['buy']; /// session["cart"].add(item); /// } /// session["cart"].cast().forEach(request.response.writeln); /// request.response.close(); /// }); /// }); /// } /// ``` abstract interface class HttpSession implements Map { /// The id of the current session. String get id; /// Destroys the session. /// /// This terminates the session and any further /// connections with this id will be given a new id and session. void destroy(); /// Sets a callback that will be called when the session is timed out. /// /// Calling this again will overwrite the previous value. void set onTimeout(void Function() callback); /// Whether the session has not yet been sent to the client. bool get isNew; } /// A MIME/IANA media type used as the value of the /// [HttpHeaders.contentTypeHeader] header. /// /// A [ContentType] is immutable. abstract interface class ContentType implements HeaderValue { /// Content type for plain text using UTF-8 encoding. /// /// text/plain; charset=utf-8 static final text = ContentType("text", "plain", charset: "utf-8"); /// Content type for HTML using UTF-8 encoding. /// /// text/html; charset=utf-8 static final html = ContentType("text", "html", charset: "utf-8"); /// Content type for JSON using UTF-8 encoding. /// /// application/json; charset=utf-8 static final json = ContentType("application", "json", charset: "utf-8"); /// Content type for binary data. /// /// application/octet-stream static final binary = ContentType("application", "octet-stream"); /// Creates a new content type object setting the primary type and /// sub type. The charset and additional parameters can also be set /// using [charset] and [parameters]. If charset is passed and /// [parameters] contains charset as well the passed [charset] will /// override the value in parameters. Keys passed in parameters will be /// converted to lower case. The `charset` entry, whether passed as `charset` /// or in `parameters`, will have its value converted to lower-case. factory ContentType( String primaryType, String subType, { String? charset, Map parameters = const {}, }) { return _ContentType(primaryType, subType, charset, parameters); } /// Creates a new content type object from parsing a Content-Type /// header value. As primary type, sub type and parameter names and /// values are not case sensitive all these values will be converted /// to lower case. Parsing this string /// /// text/html; charset=utf-8 /// /// will create a content type object with primary type "text", /// subtype "html" and parameter "charset" with value "utf-8". /// There may be more parameters supplied, but they are not recognized /// by this class. static ContentType parse(String value) { return _ContentType.parse(value); } /// Gets the MIME type and subtype, without any parameters. /// /// For the full content type `text/html;charset=utf-8`, /// the [mimeType] value is the string `text/html`. String get mimeType; /// Gets the primary type. /// /// For the full content type `text/html;charset=utf-8`, /// the [primaryType] value is the string `text`. String get primaryType; /// Gets the subtype. /// /// For the full content type `text/html;charset=utf-8`, /// the [subType] value is the string `html`. /// May be the empty string. String get subType; /// Gets the character set, if any. /// /// For the full content type `text/html;charset=utf-8`, /// the [charset] value is the string `utf-8`. String? get charset; } /// Cookie cross-site availability configuration. /// /// The value of [Cookie.sameSite], which defines whether an /// HTTP cookie is available from other sites or not. /// /// Has three possible values: [lax], [strict] and [none]. final class SameSite { /// Default value, cookie with this value will generally not be sent on /// cross-site requests, unless the user is navigated to the original site. static const lax = SameSite._("Lax"); /// Cookie with this value will never be sent on cross-site requests. static const strict = SameSite._("Strict"); /// Cookie with this value will be sent in all requests. /// /// [Cookie.secure] must also be set to true, otherwise the `none` value /// will have no effect. static const none = SameSite._("None"); static const List values = [lax, strict, none]; final String name; const SameSite._(this.name); @override String toString() => "SameSite=$name"; } /// Representation of a cookie. For cookies received by the server as Cookie /// header values only [name] and [value] properties will be set. When building a /// cookie for the 'set-cookie' header in the server and when receiving cookies /// in the client as 'set-cookie' headers all fields can be used. abstract interface class Cookie { /// The name of the cookie. /// /// Must be a `token` as specified in RFC 6265. /// /// The allowed characters in a `token` are the visible ASCII characters, /// U+0021 (`!`) through U+007E (`~`), except the separator characters: /// `(`, `)`, `<`, `>`, `@`, `,`, `;`, `:`, `\`, `"`, `/`, `[`, `]`, `?`, `=`, /// `{`, and `}`. abstract String name; /// The value of the cookie. /// /// Must be a `cookie-value` as specified in RFC 6265. /// /// The allowed characters in a cookie value are the visible ASCII characters, /// U+0021 (`!`) through U+007E (`~`) except the characters: /// `"`, `,`, `;` and `\`. /// Cookie values may be wrapped in a single pair of double quotes /// (U+0022, `"`). abstract String value; /// The time at which the cookie expires. abstract DateTime? expires; /// The number of seconds until the cookie expires. A zero or negative value /// means the cookie has expired. abstract int? maxAge; /// The domain that the cookie applies to. abstract String? domain; /// The path within the [domain] that the cookie applies to. abstract String? path; /// Whether to only send this cookie on secure connections. abstract bool secure; /// Whether the cookie is only sent in the HTTP request and is not made /// available to client side scripts. abstract bool httpOnly; /// Whether the cookie is available from other sites. /// /// This value is `null` if the SameSite attribute is not present. /// /// See [SameSite] for more information. abstract SameSite? sameSite; /// Creates a new cookie setting the name and value. /// /// [name] and [value] must be composed of valid characters according to RFC /// 6265. /// /// By default the value of `httpOnly` will be set to `true`. factory Cookie(String name, String value) => _Cookie(name, value); /// Creates a new cookie by parsing a header value from a 'set-cookie' /// header. factory Cookie.fromSetCookieValue(String value) { return _Cookie.fromSetCookieValue(value); } /// Returns the formatted string representation of the cookie. The /// string representation can be used for setting the Cookie or /// 'set-cookie' headers String toString(); } /// A server-side object /// that contains the content of and information about an HTTP request. /// /// `HttpRequest` objects are generated by an [HttpServer], /// which listens for HTTP requests on a specific host and port. /// For each request received, the HttpServer, which is a [Stream], /// generates an `HttpRequest` object and adds it to the stream. /// /// An `HttpRequest` object delivers the body content of the request /// as a stream of byte lists. /// The object also contains information about the request, /// such as the method, URI, and headers. /// /// In the following code, an HttpServer listens /// for HTTP requests. When the server receives a request, /// it uses the HttpRequest object's `method` property to dispatch requests. /// /// final HOST = InternetAddress.loopbackIPv4; /// final PORT = 80; /// /// HttpServer.bind(HOST, PORT).then((_server) { /// _server.listen((HttpRequest request) { /// switch (request.method) { /// case 'GET': /// handleGetRequest(request); /// break; /// case 'POST': /// ... /// } /// }, /// onError: handleError); // listen() failed. /// }).catchError(handleError); /// /// An HttpRequest object provides access to the associated [HttpResponse] /// object through the response property. /// The server writes its response to the body of the HttpResponse object. /// For example, here's a function that responds to a request: /// /// void handleGetRequest(HttpRequest req) { /// HttpResponse res = req.response; /// res.write('Received request ${req.method}: ${req.uri.path}'); /// res.close(); /// } abstract interface class HttpRequest implements Stream { /// The content length of the request body. /// /// If the size of the request body is not known in advance, /// this value is -1. int get contentLength; /// The method, such as 'GET' or 'POST', for the request. String get method; /// The URI for the request. /// /// This provides access to the /// path and query string for the request. Uri get uri; /// The requested URI for the request. /// /// If the request URI is absolute (e.g. 'https://www.example.com/foo') then /// it is returned as-is. Otherwise, the returned URI is reconstructed by /// using the request URI path (e.g. '/foo') and HTTP header fields. /// /// To reconstruct the scheme, the 'X-Forwarded-Proto' header is used. If it /// is not present then the socket type of the connection is used i.e. if /// the connection is made through a [SecureSocket] then the scheme is /// 'https', otherwise it is 'http'. /// /// To reconstruct the host, the 'X-Forwarded-Host' header is used. If it is /// not present then the 'Host' header is used. If neither is present then /// the host name of the server is used. Uri get requestedUri; /// The request headers. /// /// The returned [HttpHeaders] are immutable. HttpHeaders get headers; /// The cookies in the request, from the "Cookie" headers. List get cookies; /// The persistent connection state signaled by the client. bool get persistentConnection; /// The client certificate of the client making the request. /// /// This value is null if the connection is not a secure TLS or SSL connection, /// or if the server does not request a client certificate, or if the client /// does not provide one. X509Certificate? get certificate; /// The session for the given request. /// /// If the session is being initialized by this call, /// [HttpSession.isNew] is true for the returned session. /// See [HttpServer.sessionTimeout] on how to change default timeout. HttpSession get session; /// The HTTP protocol version used in the request, /// either "1.0" or "1.1". String get protocolVersion; /// Information about the client connection. /// /// Returns `null` if the socket is not available. HttpConnectionInfo? get connectionInfo; /// The [HttpResponse] object, used for sending back the response to the /// client. /// /// If the [contentLength] of the body isn't 0, and the body isn't being read, /// any write calls on the [HttpResponse] automatically drain the request /// body. HttpResponse get response; } /// An HTTP response, which returns the headers and data /// from the server to the client in response to an HTTP request. /// /// Every HttpRequest object provides access to the associated [HttpResponse] /// object through the `response` property. /// The server sends its response to the client by writing to the /// HttpResponse object. /// /// ## Writing the response /// /// This class implements [IOSink]. /// After the header has been set up, the methods /// from IOSink, such as `writeln()`, can be used to write /// the body of the HTTP response. /// Use the `close()` method to close the response and send it to the client. /// /// server.listen((HttpRequest request) { /// request.response.write('Hello, world!'); /// request.response.close(); /// }); /// /// When one of the IOSink methods is used for the /// first time, the request header is sent. Calling any methods that /// change the header after it is sent throws an exception. /// /// If no "Content-Type" header is set then a default of /// "text/plain; charset=utf-8" is used and string data written to the IOSink /// will be encoded using UTF-8. /// /// ## Setting the headers /// /// The HttpResponse object has a number of properties for setting up /// the HTTP headers of the response. /// When writing string data through the IOSink, the encoding used /// is determined from the "charset" parameter of the /// "Content-Type" header. /// /// HttpResponse response = ... /// response.headers.contentType /// = ContentType("application", "json", charset: "utf-8"); /// response.write(...); // Strings written will be UTF-8 encoded. /// /// If no charset is provided the default of ISO-8859-1 (Latin 1) will /// be used. /// /// HttpResponse response = ... /// response.headers.add(HttpHeaders.contentTypeHeader, "text/plain"); /// response.write(...); // Strings written will be ISO-8859-1 encoded. /// /// If a charset is provided but it is not recognized, then the "Content-Type" /// header will include that charset but string data will be encoded using /// ISO-8859-1 (Latin 1). abstract interface class HttpResponse implements IOSink { // TODO(ajohnsen): Add documentation of how to pipe a file to the response. /// Gets and sets the content length of the response. If the size of /// the response is not known in advance set the content length to /// -1, which is also the default if not set. int contentLength = -1; /// The status code of the response. /// /// Any integer value is accepted. For /// the official HTTP status codes use the fields from /// [HttpStatus]. If no status code is explicitly set the default /// value [HttpStatus.ok] is used. /// /// The status code must be set before the body is written /// to. Setting the status code after writing to the response body or /// closing the response will throw a `StateError`. int statusCode = HttpStatus.ok; /// The reason phrase for the response. /// /// If no reason phrase is explicitly set, a default reason phrase is provided. /// /// The reason phrase must be set before the body is written /// to. Setting the reason phrase after writing to the response body /// or closing the response will throw a [StateError]. late String reasonPhrase; /// Gets and sets the persistent connection state. The initial value /// of this property is the persistent connection state from the /// request. late bool persistentConnection; /// Set and get the [deadline] for the response. The deadline is timed from the /// time it's set. Setting a new deadline will override any previous deadline. /// When a deadline is exceeded, the response will be closed and any further /// data ignored. /// /// To disable a deadline, set the [deadline] to `null`. /// /// The [deadline] is `null` by default. Duration? deadline; /// Gets or sets if the [HttpResponse] should buffer output. /// /// Default value is `true`. /// /// __Note__: Disabling buffering of the output can result in very poor /// performance, when writing many small chunks. bool bufferOutput = true; /// Returns the response headers. /// /// The response headers can be modified until the response body is /// written to or closed. After that they become immutable. HttpHeaders get headers; /// Cookies to set in the client (in the 'set-cookie' header). List get cookies; /// Respond with a redirect to [location]. /// /// The URI in [location] should be absolute, but there are no checks /// to enforce that. /// /// By default the HTTP status code `HttpStatus.movedTemporarily` /// (`302`) is used for the redirect, but an alternative one can be /// specified using the [status] argument. /// /// This method will also call `close`, and the returned future is /// the future returned by `close`. Future redirect(Uri location, {int status = HttpStatus.movedTemporarily}); /// Detaches the underlying socket from the HTTP server. When the /// socket is detached the HTTP server will no longer perform any /// operations on it. /// /// This is normally used when an HTTP upgrade request is received /// and the communication should continue with a different protocol. /// /// If [writeHeaders] is `true`, the status line and [headers] will be written /// to the socket before it's detached. If `false`, the socket is detached /// immediately, without any data written to the socket. Default is `true`. Future detachSocket({bool writeHeaders = true}); /// Gets information about the client connection. Returns `null` if the /// socket is not available. HttpConnectionInfo? get connectionInfo; } /// An HTTP client for communicating with an HTTP server. /// /// > **Note:** You should avoid directly using `HttpClient` to make HTTP /// > requests. You can use `HttpClient` indirectly through the /// > [`IOClient`](https://pub.dev/documentation/http/latest/io_client/IOClient-class.html) /// > adapter in [`package:http`](https://pub.dev/packages/http). /// > /// > Using a higher-level library, /// > like [`package:http`](https://pub.dev/packages/http), allows you to /// > switch implementations with minimal changes to your code. For example, /// > `package:http` /// > [`Client`](https://pub.dev/documentation/http/latest/http/Client-class.html) /// > has implementations for the browser and implementations that use /// > platform native HTTP clients on Android and iOS. Unlike `HttpClient`, /// > these native implementations work with the proxies, VPNs, etc. /// /// Sends HTTP requests to an HTTP server and receives responses. /// Maintains state, including session cookies and other cookies, /// between multiple requests to the same server. /// /// HttpClient contains a number of methods to send an [HttpClientRequest] /// to an Http server and receive an [HttpClientResponse] back. /// For example, you can use the [get], [getUrl], [post], and [postUrl] methods /// for GET and POST requests, respectively. /// /// ## Making a simple GET request: an example /// /// A `getUrl` request is a two-step process, triggered by two [Future]s. /// When the first future completes with an [HttpClientRequest], the underlying /// network connection has been established, but no data has been sent. /// In the callback function for the first future, the HTTP headers and body /// can be set on the request. Either the first write to the request object /// or a call to [close] sends the request to the server. /// /// When the HTTP response is received from the server, /// the second future, which is returned by close, /// completes with an [HttpClientResponse] object. /// This object provides access to the headers and body of the response. /// The body is available as a stream implemented by `HttpClientResponse`. /// If a body is present, it must be read. Otherwise, it leads to resource /// leaks. Consider using [HttpClientResponse.drain] if the body is unused. /// /// ```dart import:convert /// var client = HttpClient(); /// try { /// HttpClientRequest request = await client.get('localhost', 80, '/file.txt'); /// // Optionally set up headers... /// // Optionally write to the request object... /// HttpClientResponse response = await request.close(); /// // Process the response /// final stringData = await response.transform(utf8.decoder).join(); /// print(stringData); /// } finally { /// client.close(); /// } /// ``` /// /// The future for [HttpClientRequest] is created by methods such as /// [getUrl] and [open]. /// /// ## HTTPS connections /// /// An `HttpClient` can make HTTPS requests, connecting to a server using /// the TLS (SSL) secure networking protocol. Calling [getUrl] with an /// https: scheme will work automatically, if the server's certificate is /// signed by a root CA (certificate authority) on the default list of /// well-known trusted CAs, compiled by Mozilla. /// /// To add a custom trusted certificate authority, or to send a client /// certificate to servers that request one, pass a [SecurityContext] object /// as the optional `context` argument to the `HttpClient` constructor. /// The desired security options can be set on the [SecurityContext] object. /// /// ## Headers /// /// All `HttpClient` requests set the following header by default: /// /// Accept-Encoding: gzip /// /// This allows the HTTP server to use gzip compression for the body if /// possible. If this behavior is not desired set the /// `Accept-Encoding` header to something else. /// To turn off gzip compression of the response, clear this header: /// /// request.headers.removeAll(HttpHeaders.acceptEncodingHeader) /// /// ## Closing the `HttpClient` /// /// `HttpClient` supports persistent connections and caches network /// connections to reuse them for multiple requests whenever /// possible. This means that network connections can be kept open for /// some time after a request has completed. Use [HttpClient.close] /// to force the `HttpClient` object to shut down and to close the idle /// network connections. /// /// ## Turning proxies on and off /// /// By default the `HttpClient` uses the proxy configuration available /// from the environment, see [findProxyFromEnvironment]. To turn off /// the use of proxies set the [findProxy] property to `null`. /// /// HttpClient client = HttpClient(); /// client.findProxy = null; abstract interface class HttpClient { static const int defaultHttpPort = 80; static const int defaultHttpsPort = 443; /// Enable logging of HTTP requests from all [HttpClient]s to the developer /// timeline. /// /// Default is `false`. static set enableTimelineLogging(bool value) { final enabled = valueOfNonNullableParamWithDefault(value, false); if (enabled != _enableTimelineLogging) { if (!const bool.fromEnvironment("dart.vm.product")) { postEvent('HttpTimelineLoggingStateChange', { 'isolateId': Service.getIsolateId(Isolate.current), 'enabled': enabled, }); } } _enableTimelineLogging = enabled; } /// Current state of HTTP request logging from all [HttpClient]s to the /// developer timeline. /// /// Default is `false`. static bool get enableTimelineLogging => _enableTimelineLogging; static bool _enableTimelineLogging = false; /// Gets and sets the idle timeout of non-active persistent (keep-alive) /// connections. /// /// The default value is 15 seconds. Duration idleTimeout = const Duration(seconds: 15); /// Gets and sets the connection timeout. /// /// When connecting to a new host exceeds this timeout, a [SocketException] /// is thrown. The timeout applies only to connections initiated after the /// timeout is set. /// /// When this is `null`, the OS default timeout is used. The default is /// `null`. Duration? connectionTimeout; /// Gets and sets the maximum number of live connections, to a single host. /// /// Increasing this number may lower performance and take up unwanted /// system resources. /// /// To disable, set to `null`. /// /// Default is `null`. int? maxConnectionsPerHost; /// Gets and sets whether the body of a response will be automatically /// uncompressed. /// /// The body of an HTTP response can be compressed. In most /// situations providing the un-compressed body is most /// convenient. Therefore the default behavior is to un-compress the /// body. However in some situations (e.g. implementing a transparent /// proxy) keeping the uncompressed stream is required. /// /// NOTE: Headers in the response are never modified. This means /// that when automatic un-compression is turned on the value of the /// header `Content-Length` will reflect the length of the original /// compressed body. Likewise the header `Content-Encoding` will also /// have the original value indicating compression. /// /// NOTE: Automatic un-compression is only performed if the /// `Content-Encoding` header value is `gzip`. /// /// This value affects all responses produced by this client after the /// value is changed. /// /// To disable, set to `false`. /// /// Default is `true`. bool autoUncompress = true; /// Gets and sets the default value of the `User-Agent` header for all requests /// generated by this [HttpClient]. /// /// The default value is `Dart/ (dart:io)`. /// /// If the userAgent is set to `null`, no default `User-Agent` header will be /// added to each request. String? userAgent; factory HttpClient({SecurityContext? context}) { HttpOverrides? overrides = HttpOverrides.current; if (overrides == null) { return _HttpClient(context); } return overrides.createHttpClient(context); } /// Opens an HTTP connection. /// /// The HTTP method to use is specified in [method], the server is /// specified using [host] and [port], and the path (including /// a possible query) is specified using [path]. /// The path may also contain a URI fragment, which will be ignored. /// /// The `Host` header for the request will be set to the value [host]:[port] /// (if [host] is an IP address, it will still be used in the `Host` header). /// This can be overridden through the [HttpClientRequest] interface before /// the request is sent. /// /// For additional information on the sequence of events during an /// HTTP transaction, and the objects returned by the futures, see /// the overall documentation for the class [HttpClient]. Future open( String method, String host, int port, String path, ); /// Opens an HTTP connection. /// /// The HTTP method is specified in [method] and the URL to use in /// [url]. /// /// The `Host` header for the request will be set to the value /// [Uri.host]:[Uri.port] from [url] (if `url.host` is an IP address, it will /// still be used in the `Host` header). This can be overridden through the /// [HttpClientRequest] interface before the request is sent. /// /// For additional information on the sequence of events during an /// HTTP transaction, and the objects returned by the futures, see /// the overall documentation for the class [HttpClient]. Future openUrl(String method, Uri url); /// Opens an HTTP connection using the GET method. /// /// The server is specified using [host] and [port], and the path /// (including a possible query) is specified using /// [path]. /// /// See [open] for details. Future get(String host, int port, String path); /// Opens an HTTP connection using the GET method. /// /// The URL to use is specified in [url]. /// /// See [openUrl] for details. Future getUrl(Uri url); /// Opens an HTTP connection using the POST method. /// /// The server is specified using [host] and [port], and the path /// (including a possible query) is specified using /// [path]. /// /// See [open] for details. Future post(String host, int port, String path); /// Opens an HTTP connection using the POST method. /// /// The URL to use is specified in [url]. /// /// See [openUrl] for details. Future postUrl(Uri url); /// Opens an HTTP connection using the PUT method. /// /// The server is specified using [host] and [port], and the path /// (including a possible query) is specified using [path]. /// /// See [open] for details. Future put(String host, int port, String path); /// Opens an HTTP connection using the PUT method. /// /// The URL to use is specified in [url]. /// /// See [openUrl] for details. Future putUrl(Uri url); /// Opens an HTTP connection using the DELETE method. /// /// The server is specified using [host] and [port], and the path /// (including a possible query) is specified using [path]. /// /// See [open] for details. Future delete(String host, int port, String path); /// Opens an HTTP connection using the DELETE method. /// /// The URL to use is specified in [url]. /// /// See [openUrl] for details. Future deleteUrl(Uri url); /// Opens an HTTP connection using the PATCH method. /// /// The server is specified using [host] and [port], and the path /// (including a possible query) is specified using [path]. /// /// See [open] for details. Future patch(String host, int port, String path); /// Opens an HTTP connection using the PATCH method. /// /// The URL to use is specified in [url]. /// /// See [openUrl] for details. Future patchUrl(Uri url); /// Opens an HTTP connection using the HEAD method. /// /// The server is specified using [host] and [port], and the path /// (including a possible query) is specified using [path]. /// /// See [open] for details. Future head(String host, int port, String path); /// Opens an HTTP connection using the HEAD method. /// /// The URL to use is specified in [url]. /// /// See [openUrl] for details. Future headUrl(Uri url); /// Sets the function to be called when a site is requesting /// authentication. /// /// The URL requested, the authentication scheme and the security realm /// from the server are passed in the arguments `f.url`, `f.scheme` and /// `f.realm`. /// /// The function returns a [Future] which should complete when the /// authentication has been resolved. If credentials cannot be /// provided the [Future] should complete with `false`. If /// credentials are available the function should add these using /// [addCredentials] before completing the [Future] with the value /// `true`. /// /// If the [Future] completes with `true` the request will be retried /// using the updated credentials, however, the retried request will not /// carry the original request payload. Otherwise response processing will /// continue normally. /// /// If it is known that the remote server requires authentication for all /// requests, it is advisable to use [addCredentials] directly, or manually /// set the `'authorization'` header on the request to avoid the overhead /// of a failed request, or issues due to missing request payload on retried /// request. void set authenticate( Future Function(Uri url, String scheme, String? realm)? f, ); /// Add credentials to be used for authorizing HTTP requests. void addCredentials(Uri url, String realm, HttpClientCredentials credentials); /// Sets the function used to create socket connections. /// /// The URL requested (e.g. through [getUrl]) and proxy configuration /// (`f.proxyHost` and `f.proxyPort`) are passed as arguments. `f.proxyHost` /// and `f.proxyPort` will be `null` if the connection is not made through /// a proxy. /// /// Since connections may be reused based on host and port, it is important /// that the function not ignore `f.proxyHost` and `f.proxyPort` if they are /// not `null`. If proxies are not meaningful for the returned [Socket], you /// can set [findProxy] to use a direct connection. /// /// For example: /// /// ```dart /// import "dart:io"; /// /// void main() async { /// HttpClient client = HttpClient() /// ..connectionFactory = (Uri uri, String? proxyHost, int? proxyPort) { /// assert(proxyHost == null); /// assert(proxyPort == null); /// var address = InternetAddress("/var/run/docker.sock", /// type: InternetAddressType.unix); /// return Socket.startConnect(address, 0); /// } /// ..findProxy = (Uri uri) => 'DIRECT'; /// /// final request = await client.getUrl(Uri.parse("http://ignored/v1.41/info")); /// final response = await request.close(); /// print(response.statusCode); /// await response.drain(); /// client.close(); /// } /// ``` void set connectionFactory( Future> Function( Uri url, String? proxyHost, int? proxyPort, )? f, ); /// Sets the function used to resolve the proxy server to be used for /// opening an HTTP connection to the specified `url`. If this /// function is not set, direct connections will always be used. /// /// The string returned by [f] must be in the format used by browser /// PAC (proxy auto-config) scripts. That is either /// /// "DIRECT" /// /// for using a direct connection or /// /// "PROXY host:port" /// /// for using the proxy server `host` on port `port`. /// /// A configuration can contain several configuration elements /// separated by semicolons, e.g. /// /// "PROXY host:port; PROXY host2:port2; DIRECT" /// /// The static function [findProxyFromEnvironment] on this class can /// be used to implement proxy server resolving based on environment /// variables. void set findProxy(String Function(Uri url)? f); /// Function for resolving the proxy server to be used for an HTTP /// connection from the proxy configuration specified through /// environment variables. /// /// The following environment variables are taken into account: /// /// http_proxy /// https_proxy /// no_proxy /// HTTP_PROXY /// HTTPS_PROXY /// NO_PROXY /// /// [:http_proxy:] and [:HTTP_PROXY:] specify the proxy server to use for /// http:// urls. Use the format [:hostname:port:]. If no port is used a /// default of 1080 will be used. If both are set the lower case one takes /// precedence. /// /// [:https_proxy:] and [:HTTPS_PROXY:] specify the proxy server to use for /// https:// urls. Use the format [:hostname:port:]. If no port is used a /// default of 1080 will be used. If both are set the lower case one takes /// precedence. /// /// [:no_proxy:] and [:NO_PROXY:] specify a comma separated list of /// postfixes of hostnames for which not to use the proxy /// server. E.g. the value "localhost,127.0.0.1" will make requests /// to both "localhost" and "127.0.0.1" not use a proxy. If both are set /// the lower case one takes precedence. /// /// To activate this way of resolving proxies assign this function to /// the [findProxy] property on the [HttpClient]. /// /// HttpClient client = HttpClient(); /// client.findProxy = HttpClient.findProxyFromEnvironment; /// /// If you don't want to use the system environment you can use a /// different one by wrapping the function. /// /// HttpClient client = HttpClient(); /// client.findProxy = (url) { /// return HttpClient.findProxyFromEnvironment( /// url, environment: {"http_proxy": ..., "no_proxy": ...}); /// } /// /// If a proxy requires authentication it is possible to configure /// the username and password as well. Use the format /// [:username:password@hostname:port:] to include the username and /// password. Alternatively the API [addProxyCredentials] can be used /// to set credentials for proxies which require authentication. static String findProxyFromEnvironment( Uri url, { Map? environment, }) { HttpOverrides? overrides = HttpOverrides.current; if (overrides == null) { return _HttpClient._findProxyFromEnvironment(url, environment); } return overrides.findProxyFromEnvironment(url, environment); } /// Sets the function to be called when a proxy is requesting /// authentication. /// /// Information on the proxy in use, the authentication scheme /// and the security realm for the authentication /// are passed in the arguments `f.host`, `f.port`, `f.scheme` and `f.realm`. /// /// The function returns a [Future] which should complete when the /// authentication has been resolved. If credentials cannot be /// provided the [Future] should complete with `false`. If /// credentials are available the function should add these using /// [addProxyCredentials] before completing the [Future] with the value /// `true`. /// /// If the [Future] completes with `true` the request will be retried /// using the updated credentials. Otherwise response processing will /// continue normally. void set authenticateProxy( Future Function(String host, int port, String scheme, String? realm)? f, ); /// Add credentials to be used for authorizing HTTP proxies. void addProxyCredentials( String host, int port, String realm, HttpClientCredentials credentials, ); /// Sets a callback that will decide whether to accept a secure connection /// with a server certificate that cannot be authenticated by any of our /// trusted root certificates. /// /// When a secure HTTP request is made, using this HttpClient, and the /// server returns a server certificate that cannot be authenticated, the /// callback is called asynchronously with the [X509Certificate] object and /// the server's hostname and port. If the value of [badCertificateCallback] /// is `null`, the bad certificate is rejected, as if the callback /// returned `false` /// /// If the callback returns true, the secure connection is accepted and the /// `Future` that was returned from the call making the /// request completes with a valid HttpRequest object. If the callback returns /// false, the `Future` completes with an exception. /// /// If a bad certificate is received on a connection attempt, the library calls /// the function that was the value of badCertificateCallback at the time /// the request is made, even if the value of badCertificateCallback /// has changed since then. void set badCertificateCallback( bool Function(X509Certificate cert, String host, int port)? callback, ); /// Sets a callback that will be called when new TLS keys are exchanged with /// the server. It will receive one line of text in /// [NSS Key Log Format](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format) /// for each call. Writing these lines to a file will allow tools (such as /// [Wireshark](https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption)) /// to decrypt communication between the this client and the server. This is /// meant to allow network-level debugging of secure sockets and should not /// be used in production code. For example: /// /// final log = File('keylog.txt'); /// final client = HttpClient(); /// client.keyLog = (line) => log.writeAsStringSync(line, /// mode: FileMode.append); void set keyLog(Function(String line)? callback); /// Shuts down the HTTP client. /// /// If [force] is `false` (the default) the [HttpClient] will be kept alive /// until all active connections are done. If [force] is `true` any active /// connections will be closed to immediately release all resources. These /// closed connections will receive an error event to indicate that the client /// was shut down. In both cases trying to establish a new connection after /// calling [close] will throw an exception. void close({bool force = false}); } /// HTTP request for a client connection. /// /// To set up a request, set the headers using the headers property /// provided in this class and write the data to the body of the request. /// `HttpClientRequest` is an [IOSink]. Use the methods from IOSink, /// such as `writeCharCode()`, to write the body of the HTTP /// request. When one of the IOSink methods is used for the first /// time, the request header is sent. Calling any methods that /// change the header after it is sent throws an exception. /// /// When writing string data through the [IOSink] the /// encoding used is determined from the "charset" parameter of /// the "Content-Type" header. /// /// ```dart import:convert /// var client = HttpClient(); /// HttpClientRequest request = await client.get('localhost', 80, '/file.txt'); /// request.headers.contentType = /// ContentType('application', 'json', charset: 'utf-8'); /// request.write('text content👍🎯'); // Strings written will be UTF-8 encoded. /// ``` /// /// If no charset is provided the default of ISO-8859-1 (Latin 1) is used. /// /// ```dart /// var client = HttpClient(); /// HttpClientRequest request = await client.get('localhost', 80, '/file.txt'); /// request.headers.add(HttpHeaders.contentTypeHeader, "text/plain"); /// request.write('blåbærgrød'); // Strings written will be ISO-8859-1 encoded /// ``` /// /// An exception is thrown if you use an unsupported encoding and the /// `write()` method being used takes a string parameter. abstract interface class HttpClientRequest implements IOSink { /// The requested persistent connection state. /// /// The default value is `true`. bool persistentConnection = true; /// Whether to follow redirects automatically. /// /// Set this property to `false` if this request should not /// automatically follow redirects. The default is `true`. /// /// Automatic redirect will only happen for "GET" and "HEAD" requests /// and only for the status codes [HttpStatus.movedPermanently] /// (301), [HttpStatus.found] (302), /// [HttpStatus.movedTemporarily] (302, alias for /// [HttpStatus.found]), [HttpStatus.seeOther] (303), /// [HttpStatus.temporaryRedirect] (307) and /// [HttpStatus.permanentRedirect] (308). For /// [HttpStatus.seeOther] (303) automatic redirect will also happen /// for "POST" requests with the method changed to "GET" when /// following the redirect. /// /// All headers added to the request will be added to the redirection /// request(s) except when forwarding sensitive headers like /// "Authorization", "WWW-Authenticate", and "Cookie". Those headers /// will be skipped if following a redirect to a domain that is not a /// subdomain match or exact match of the initial domain. /// For example, a redirect from "foo.com" to either "foo.com" or /// "sub.foo.com" will forward the sensitive headers, but a redirect to /// "bar.com" will not. /// /// Any body send with the request will not be part of the redirection /// request(s). /// /// For precise control of redirect handling, set this property to `false` /// and make a separate HTTP request to process the redirect. For example: /// /// ```dart /// final client = HttpClient(); /// var uri = Uri.parse("http://localhost/"); /// var request = await client.getUrl(uri); /// request.followRedirects = false; /// var response = await request.close(); /// while (response.isRedirect) { /// response.drain(); /// final location = response.headers.value(HttpHeaders.locationHeader); /// if (location != null) { /// uri = uri.resolve(location); /// request = await client.getUrl(uri); /// // Set the body or headers as desired. /// request.followRedirects = false; /// response = await request.close(); /// } /// } /// // Do something with the final response. /// ``` bool followRedirects = true; /// Set this property to the maximum number of redirects to follow /// when [followRedirects] is `true`. If this number is exceeded /// an error event will be added with a [RedirectException]. /// /// The default value is 5. int maxRedirects = 5; /// The method of the request. String get method; /// The uri of the request. Uri get uri; /// Gets and sets the content length of the request. /// /// If the size of the request is not known in advance set content length to /// -1, which is also the default. int contentLength = -1; /// Gets or sets if the [HttpClientRequest] should buffer output. /// /// Default value is `true`. /// /// __Note__: Disabling buffering of the output can result in very poor /// performance, when writing many small chunks. bool bufferOutput = true; /// Returns the client request headers. /// /// The client request headers can be modified until the client /// request body is written to or closed. After that they become /// immutable. HttpHeaders get headers; /// Cookies to present to the server (in the 'cookie' header). List get cookies; /// An [HttpClientResponse] future that will complete once the response is /// available. /// /// If an error occurs before the response is available, this future will /// complete with an error. Future get done; /// Close the request for input. Returns the value of [done]. Future close(); /// Gets information about the client connection. /// /// Returns `null` if the socket is not available. HttpConnectionInfo? get connectionInfo; /// Aborts the client connection. /// /// If the connection has not yet completed, the request is aborted and the /// [done] future (also returned by [close]) is completed with the provided /// [exception] and [stackTrace]. /// If [exception] is omitted, it defaults to an [HttpException], and if /// [stackTrace] is omitted, it defaults to [StackTrace.empty]. /// /// If the [done] future has already completed, aborting has no effect. /// /// Using the [IOSink] methods (e.g., [write] and [add]) has no effect after /// the request has been aborted /// /// ```dart import:async /// var client = HttpClient(); /// HttpClientRequest request = await client.get('localhost', 80, '/file.txt'); /// request.write('request content'); /// Timer(Duration(seconds: 1), () { /// request.abort(); /// }); /// request.close().then((response) { /// // If response comes back before abort, this callback will be called. /// }, onError: (e) { /// // If abort() called before response is available, onError will fire. /// }); /// ``` void abort([Object? exception, StackTrace? stackTrace]); } /// HTTP response for a client connection. /// /// The body of an [HttpClientResponse] object is a [Stream] of data from the /// server. Use [Stream] methods like [`transform`][Stream.transform] and /// [`join`][Stream.join] to access the data. /// /// ```dart import:convert /// var client = HttpClient(); /// try { /// HttpClientRequest request = await client.get('localhost', 80, '/file.txt'); /// HttpClientResponse response = await request.close(); /// final stringData = await response.transform(utf8.decoder).join(); /// print(stringData); /// } finally { /// client.close(); /// } /// ``` abstract interface class HttpClientResponse implements Stream> { /// Returns the status code. /// /// The status code must be set before the body is written /// to. Setting the status code after writing to the body will throw /// a `StateError`. int get statusCode; /// Returns the reason phrase associated with the status code. /// /// The reason phrase must be set before the body is written /// to. Setting the reason phrase after writing to the body will throw /// a `StateError`. String get reasonPhrase; /// Returns the content length of the response body. Returns -1 if the size of /// the response body is not known in advance. /// /// If the content length needs to be set, it must be set before the /// body is written to. Setting the content length after writing to the body /// will throw a `StateError`. int get contentLength; /// The compression state of the response. /// /// This specifies whether the response bytes were compressed when they were /// received across the wire and whether callers will receive compressed /// or uncompressed bytes when they listed to this response's byte stream. HttpClientResponseCompressionState get compressionState; /// Gets the persistent connection state returned by the server. /// /// If the persistent connection state needs to be set, it must be /// set before the body is written to. Setting the persistent connection state /// after writing to the body will throw a `StateError`. bool get persistentConnection; /// Returns whether the status code is one of the normal redirect /// codes [HttpStatus.movedPermanently], [HttpStatus.found], /// [HttpStatus.movedTemporarily], [HttpStatus.seeOther] and /// [HttpStatus.temporaryRedirect]. bool get isRedirect; /// Returns the series of redirects this connection has been through. The /// list will be empty if no redirects were followed. [redirects] will be /// updated both in the case of an automatic and a manual redirect. List get redirects; /// Redirects this connection to a new URL. The default value for /// [method] is the method for the current request. The default value /// for [url] is the value of the [HttpHeaders.locationHeader] header of /// the current response. All body data must have been read from the /// current response before calling [redirect]. /// /// All headers added to the request will be added to the redirection /// request. However, any body sent with the request will not be /// part of the redirection request. /// /// If [followLoops] is set to `true`, redirect will follow the redirect, /// even if the URL was already visited. The default value is `false`. /// /// The method will ignore [HttpClientRequest.maxRedirects] /// and will always perform the redirect. Future redirect([ String? method, Uri? url, bool? followLoops, ]); /// Returns the client response headers. /// /// The client response headers are immutable. HttpHeaders get headers; /// Detach the underlying socket from the HTTP client. When the /// socket is detached the HTTP client will no longer perform any /// operations on it. /// /// This is normally used when an HTTP upgrade is negotiated and the /// communication should continue with a different protocol. Future detachSocket(); /// Cookies set by the server (from the 'set-cookie' header). List get cookies; /// Returns the certificate of the HTTPS server providing the response. /// Returns null if the connection is not a secure TLS or SSL connection. X509Certificate? get certificate; /// Gets information about the client connection. Returns `null` if the socket /// is not available. HttpConnectionInfo? get connectionInfo; } /// Enum that specifies the compression state of the byte stream of an /// [HttpClientResponse]. /// /// The values herein allow callers to answer the following questions as they /// pertain to an [HttpClientResponse]: /// /// * Can the value of the response's `Content-Length` HTTP header be trusted? /// * Does the caller need to manually decompress the response's byte stream? /// /// This enum is accessed via the [HttpClientResponse.compressionState] value. enum HttpClientResponseCompressionState { /// The body of the HTTP response was received and remains in an uncompressed /// state. /// /// In this state, the value of the `Content-Length` HTTP header, if /// specified (non-negative), should match the number of bytes produced by /// the response's byte stream. notCompressed, /// The body of the HTTP response was originally compressed, but by virtue of /// the [HttpClient.autoUncompress] configuration option, it has been /// automatically uncompressed. /// /// HTTP headers are not modified, so when a response has been uncompressed /// in this way, the value of the `Content-Length` HTTP header cannot be /// trusted, as it will contain the compressed content length, whereas the /// stream of bytes produced by the response will contain uncompressed bytes. decompressed, /// The body of the HTTP response contains compressed bytes. /// /// In this state, the value of the `Content-Length` HTTP header, if /// specified (non-negative), should match the number of bytes produced by /// the response's byte stream. /// /// If the caller wishes to manually uncompress the body of the response, /// it should consult the value of the `Content-Encoding` HTTP header to see /// what type of compression has been applied. See /// for more information. compressed, } /// Represents credentials for authentication in [HttpClient]. /// /// Subtypes of [HttpClientCredentials] can be passed to /// [HttpClient.addCredentials] or [HttpClient.addProxyCredentials]. abstract interface class HttpClientCredentials {} /// Represents credentials for basic authentication. /// /// See https://datatracker.ietf.org/doc/html/rfc7617 abstract final class HttpClientBasicCredentials implements HttpClientCredentials { factory HttpClientBasicCredentials(String username, String password) => _HttpClientBasicCredentials(username, password); } /// Represents credentials for bearer token authentication. /// /// See https://datatracker.ietf.org/doc/html/rfc6750 abstract final class HttpClientBearerCredentials implements HttpClientCredentials { factory HttpClientBearerCredentials(String token) => _HttpClientBearerCredentials(token); } /// Represents credentials for digest authentication. /// /// Digest authentication is only supported for servers using the MD5 algorithm /// and quality of protection (qop) of either "none" or "auth". /// /// See https://datatracker.ietf.org/doc/html/rfc7616 abstract final class HttpClientDigestCredentials implements HttpClientCredentials { factory HttpClientDigestCredentials(String username, String password) => _HttpClientDigestCredentials(username, password); } /// Information about an [HttpRequest], [HttpResponse], [HttpClientRequest], or /// [HttpClientResponse] connection. abstract interface class HttpConnectionInfo { InternetAddress get remoteAddress; int get remotePort; int get localPort; } /// Redirect information. abstract interface class RedirectInfo { /// Returns the status code used for the redirect. int get statusCode; /// Returns the method used for the redirect. String get method; /// Returns the location for the redirect. Uri get location; } class HttpException implements IOException { final String message; final Uri? uri; const HttpException(this.message, {this.uri}); String toString() { var b = StringBuffer() ..write('HttpException: ') ..write(message); var uri = this.uri; if (uri != null) { b.write(', uri = $uri'); } return b.toString(); } } class RedirectException implements HttpException { final String message; final List redirects; const RedirectException(this.message, this.redirects); String toString() => "RedirectException: $message"; Uri? get uri => redirects.isEmpty ? null : redirects.last.location; }