mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
feat(ext/http): auto-compression of fixed response bodies (#13769)
Co-authored-by: Ryan Dahl <ry@tinyclouds.org> Co-authored-by: Satya Rohith <me@satyarohith.com> Co-authored-by: Luca Casonato <lucacasonato@yahoo.com>
This commit is contained in:
parent
99904a668e
commit
d1db500cda
5 changed files with 1342 additions and 4 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -971,10 +971,15 @@ name = "deno_http"
|
|||
version = "0.31.0"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"brotli",
|
||||
"bytes",
|
||||
"cache_control",
|
||||
"deno_core",
|
||||
"deno_websocket",
|
||||
"flate2",
|
||||
"fly-accept-encoding",
|
||||
"hyper",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"ring",
|
||||
"serde",
|
||||
|
@ -1505,6 +1510,16 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fly-accept-encoding"
|
||||
version = "0.2.0-alpha.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "741d3e4ac3bcebc022cd90e7d1ce376515a73db2d53ba7fd3a7e581d6db7fa97"
|
||||
dependencies = [
|
||||
"http",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
|
|
@ -95,7 +95,7 @@ Deno.test(
|
|||
|
||||
const resp = new Uint8Array(200);
|
||||
const readResult = await conn.read(resp);
|
||||
assertEquals(readResult, 115);
|
||||
assertEquals(readResult, 138);
|
||||
|
||||
conn.close();
|
||||
|
||||
|
@ -1165,7 +1165,7 @@ Deno.test(
|
|||
|
||||
const resp = new Uint8Array(200);
|
||||
const readResult = await conn.read(resp);
|
||||
assertEquals(readResult, 115);
|
||||
assertEquals(readResult, 138);
|
||||
|
||||
conn.close();
|
||||
|
||||
|
@ -1173,6 +1173,505 @@ Deno.test(
|
|||
},
|
||||
);
|
||||
|
||||
/* Automatic Body Compression */
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
Deno.test({
|
||||
name: "http server compresses body",
|
||||
permissions: { net: true },
|
||||
async fn() {
|
||||
const hostname = "localhost";
|
||||
const port = 4501;
|
||||
|
||||
async function server() {
|
||||
const listener = Deno.listen({ hostname, port });
|
||||
const tcpConn = await listener.accept();
|
||||
const httpConn = Deno.serveHttp(tcpConn);
|
||||
const e = await httpConn.nextRequest();
|
||||
assert(e);
|
||||
const { request, respondWith } = e;
|
||||
assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
|
||||
const response = new Response(
|
||||
JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
|
||||
{
|
||||
headers: { "content-type": "application/json" },
|
||||
},
|
||||
);
|
||||
await respondWith(response);
|
||||
httpConn.close();
|
||||
listener.close();
|
||||
}
|
||||
|
||||
async function client() {
|
||||
const url = `http://${hostname}:${port}/`;
|
||||
const cmd = [
|
||||
"curl",
|
||||
"-I",
|
||||
"--request",
|
||||
"GET",
|
||||
"--url",
|
||||
url,
|
||||
"--header",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
];
|
||||
const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
|
||||
const status = await proc.status();
|
||||
assert(status.success);
|
||||
const output = decoder.decode(await proc.output());
|
||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||
assert(output.includes("content-encoding: gzip\r\n"));
|
||||
proc.close();
|
||||
}
|
||||
|
||||
await Promise.all([server(), client()]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "http server doesn't compress small body",
|
||||
permissions: { net: true },
|
||||
async fn() {
|
||||
const hostname = "localhost";
|
||||
const port = 4501;
|
||||
|
||||
async function server() {
|
||||
const listener = Deno.listen({ hostname, port });
|
||||
const tcpConn = await listener.accept();
|
||||
const httpConn = Deno.serveHttp(tcpConn);
|
||||
const e = await httpConn.nextRequest();
|
||||
assert(e);
|
||||
const { request, respondWith } = e;
|
||||
assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
|
||||
const response = new Response(
|
||||
JSON.stringify({ hello: "deno" }),
|
||||
{
|
||||
headers: { "content-type": "application/json" },
|
||||
},
|
||||
);
|
||||
await respondWith(response);
|
||||
httpConn.close();
|
||||
listener.close();
|
||||
}
|
||||
|
||||
async function client() {
|
||||
const url = `http://${hostname}:${port}/`;
|
||||
const cmd = [
|
||||
"curl",
|
||||
"-I",
|
||||
"--request",
|
||||
"GET",
|
||||
"--url",
|
||||
url,
|
||||
"--header",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
];
|
||||
const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
|
||||
const status = await proc.status();
|
||||
assert(status.success);
|
||||
const output = decoder.decode(await proc.output()).toLocaleLowerCase();
|
||||
assert(output.includes("vary: accept-encoding\r\n"));
|
||||
assert(!output.includes("content-encoding: "));
|
||||
proc.close();
|
||||
}
|
||||
|
||||
await Promise.all([server(), client()]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "http server respects accept-encoding weights",
|
||||
permissions: { net: true },
|
||||
async fn() {
|
||||
const hostname = "localhost";
|
||||
const port = 4501;
|
||||
|
||||
async function server() {
|
||||
const listener = Deno.listen({ hostname, port });
|
||||
const tcpConn = await listener.accept();
|
||||
const httpConn = Deno.serveHttp(tcpConn);
|
||||
const e = await httpConn.nextRequest();
|
||||
assert(e);
|
||||
const { request, respondWith } = e;
|
||||
assertEquals(
|
||||
request.headers.get("Accept-Encoding"),
|
||||
"gzip;q=0.8, br;q=1.0, *;q=0.1",
|
||||
);
|
||||
const response = new Response(
|
||||
JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
|
||||
{
|
||||
headers: { "content-type": "application/json" },
|
||||
},
|
||||
);
|
||||
await respondWith(response);
|
||||
httpConn.close();
|
||||
listener.close();
|
||||
}
|
||||
|
||||
async function client() {
|
||||
const url = `http://${hostname}:${port}/`;
|
||||
const cmd = [
|
||||
"curl",
|
||||
"-I",
|
||||
"--request",
|
||||
"GET",
|
||||
"--url",
|
||||
url,
|
||||
"--header",
|
||||
"Accept-Encoding: gzip;q=0.8, br;q=1.0, *;q=0.1",
|
||||
];
|
||||
const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
|
||||
const status = await proc.status();
|
||||
assert(status.success);
|
||||
const output = decoder.decode(await proc.output());
|
||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||
assert(output.includes("content-encoding: br\r\n"));
|
||||
proc.close();
|
||||
}
|
||||
|
||||
await Promise.all([server(), client()]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "http server augments vary header",
|
||||
permissions: { net: true },
|
||||
async fn() {
|
||||
const hostname = "localhost";
|
||||
const port = 4501;
|
||||
|
||||
async function server() {
|
||||
const listener = Deno.listen({ hostname, port });
|
||||
const tcpConn = await listener.accept();
|
||||
const httpConn = Deno.serveHttp(tcpConn);
|
||||
const e = await httpConn.nextRequest();
|
||||
assert(e);
|
||||
const { request, respondWith } = e;
|
||||
assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
|
||||
const response = new Response(
|
||||
JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
|
||||
{
|
||||
headers: { "content-type": "application/json", vary: "Accept" },
|
||||
},
|
||||
);
|
||||
await respondWith(response);
|
||||
httpConn.close();
|
||||
listener.close();
|
||||
}
|
||||
|
||||
async function client() {
|
||||
const url = `http://${hostname}:${port}/`;
|
||||
const cmd = [
|
||||
"curl",
|
||||
"-I",
|
||||
"--request",
|
||||
"GET",
|
||||
"--url",
|
||||
url,
|
||||
"--header",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
];
|
||||
const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
|
||||
const status = await proc.status();
|
||||
assert(status.success);
|
||||
const output = decoder.decode(await proc.output());
|
||||
assert(output.includes("vary: Accept-Encoding, Accept\r\n"));
|
||||
assert(output.includes("content-encoding: gzip\r\n"));
|
||||
proc.close();
|
||||
}
|
||||
|
||||
await Promise.all([server(), client()]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "http server weakens etag header",
|
||||
permissions: { net: true },
|
||||
async fn() {
|
||||
const hostname = "localhost";
|
||||
const port = 4501;
|
||||
|
||||
async function server() {
|
||||
const listener = Deno.listen({ hostname, port });
|
||||
const tcpConn = await listener.accept();
|
||||
const httpConn = Deno.serveHttp(tcpConn);
|
||||
const e = await httpConn.nextRequest();
|
||||
assert(e);
|
||||
const { request, respondWith } = e;
|
||||
assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
|
||||
const response = new Response(
|
||||
JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
etag: "33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
},
|
||||
},
|
||||
);
|
||||
await respondWith(response);
|
||||
httpConn.close();
|
||||
listener.close();
|
||||
}
|
||||
|
||||
async function client() {
|
||||
const url = `http://${hostname}:${port}/`;
|
||||
const cmd = [
|
||||
"curl",
|
||||
"-I",
|
||||
"--request",
|
||||
"GET",
|
||||
"--url",
|
||||
url,
|
||||
"--header",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
];
|
||||
const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
|
||||
const status = await proc.status();
|
||||
assert(status.success);
|
||||
const output = decoder.decode(await proc.output());
|
||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||
assert(
|
||||
output.includes("etag: W/33a64df551425fcc55e4d42a148795d9f25f89d4\r\n"),
|
||||
);
|
||||
assert(output.includes("content-encoding: gzip\r\n"));
|
||||
proc.close();
|
||||
}
|
||||
|
||||
await Promise.all([server(), client()]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "http server passes through weak etag header",
|
||||
permissions: { net: true },
|
||||
async fn() {
|
||||
const hostname = "localhost";
|
||||
const port = 4501;
|
||||
|
||||
async function server() {
|
||||
const listener = Deno.listen({ hostname, port });
|
||||
const tcpConn = await listener.accept();
|
||||
const httpConn = Deno.serveHttp(tcpConn);
|
||||
const e = await httpConn.nextRequest();
|
||||
assert(e);
|
||||
const { request, respondWith } = e;
|
||||
assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
|
||||
const response = new Response(
|
||||
JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
etag: "W/33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
},
|
||||
},
|
||||
);
|
||||
await respondWith(response);
|
||||
httpConn.close();
|
||||
listener.close();
|
||||
}
|
||||
|
||||
async function client() {
|
||||
const url = `http://${hostname}:${port}/`;
|
||||
const cmd = [
|
||||
"curl",
|
||||
"-I",
|
||||
"--request",
|
||||
"GET",
|
||||
"--url",
|
||||
url,
|
||||
"--header",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
];
|
||||
const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
|
||||
const status = await proc.status();
|
||||
assert(status.success);
|
||||
const output = decoder.decode(await proc.output());
|
||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||
assert(
|
||||
output.includes("etag: W/33a64df551425fcc55e4d42a148795d9f25f89d4\r\n"),
|
||||
);
|
||||
assert(output.includes("content-encoding: gzip\r\n"));
|
||||
proc.close();
|
||||
}
|
||||
|
||||
await Promise.all([server(), client()]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "http server doesn't compress body when no-transform is set",
|
||||
permissions: { net: true },
|
||||
async fn() {
|
||||
const hostname = "localhost";
|
||||
const port = 4501;
|
||||
|
||||
async function server() {
|
||||
const listener = Deno.listen({ hostname, port });
|
||||
const tcpConn = await listener.accept();
|
||||
const httpConn = Deno.serveHttp(tcpConn);
|
||||
const e = await httpConn.nextRequest();
|
||||
assert(e);
|
||||
const { request, respondWith } = e;
|
||||
assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
|
||||
const response = new Response(
|
||||
JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"cache-control": "no-transform",
|
||||
},
|
||||
},
|
||||
);
|
||||
await respondWith(response);
|
||||
httpConn.close();
|
||||
listener.close();
|
||||
}
|
||||
|
||||
async function client() {
|
||||
const url = `http://${hostname}:${port}/`;
|
||||
const cmd = [
|
||||
"curl",
|
||||
"-I",
|
||||
"--request",
|
||||
"GET",
|
||||
"--url",
|
||||
url,
|
||||
"--header",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
];
|
||||
const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
|
||||
const status = await proc.status();
|
||||
assert(status.success);
|
||||
const output = decoder.decode(await proc.output());
|
||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||
assert(!output.includes("content-encoding: "));
|
||||
proc.close();
|
||||
}
|
||||
|
||||
await Promise.all([server(), client()]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "http server doesn't compress body when content-range is set",
|
||||
permissions: { net: true },
|
||||
async fn() {
|
||||
const hostname = "localhost";
|
||||
const port = 4501;
|
||||
|
||||
async function server() {
|
||||
const listener = Deno.listen({ hostname, port });
|
||||
const tcpConn = await listener.accept();
|
||||
const httpConn = Deno.serveHttp(tcpConn);
|
||||
const e = await httpConn.nextRequest();
|
||||
assert(e);
|
||||
const { request, respondWith } = e;
|
||||
assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
|
||||
const response = new Response(
|
||||
JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"content-range": "bytes 200-100/67589",
|
||||
},
|
||||
},
|
||||
);
|
||||
await respondWith(response);
|
||||
httpConn.close();
|
||||
listener.close();
|
||||
}
|
||||
|
||||
async function client() {
|
||||
const url = `http://${hostname}:${port}/`;
|
||||
const cmd = [
|
||||
"curl",
|
||||
"-I",
|
||||
"--request",
|
||||
"GET",
|
||||
"--url",
|
||||
url,
|
||||
"--header",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
];
|
||||
const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
|
||||
const status = await proc.status();
|
||||
assert(status.success);
|
||||
const output = decoder.decode(await proc.output());
|
||||
assert(output.includes("vary: Accept-Encoding\r\n"));
|
||||
assert(!output.includes("content-encoding: "));
|
||||
proc.close();
|
||||
}
|
||||
|
||||
await Promise.all([server(), client()]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "http server doesn't compress streamed bodies",
|
||||
permissions: { net: true },
|
||||
async fn() {
|
||||
const hostname = "localhost";
|
||||
const port = 4501;
|
||||
|
||||
async function server() {
|
||||
const encoder = new TextEncoder();
|
||||
const listener = Deno.listen({ hostname, port });
|
||||
const tcpConn = await listener.accept();
|
||||
const httpConn = Deno.serveHttp(tcpConn);
|
||||
const e = await httpConn.nextRequest();
|
||||
assert(e);
|
||||
const { request, respondWith } = e;
|
||||
assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
|
||||
const bodyInit = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
JSON.stringify({
|
||||
hello: "deno",
|
||||
now: "with",
|
||||
compressed: "body",
|
||||
}),
|
||||
),
|
||||
);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
const response = new Response(
|
||||
bodyInit,
|
||||
{
|
||||
headers: { "content-type": "application/json", vary: "Accept" },
|
||||
},
|
||||
);
|
||||
await respondWith(response);
|
||||
httpConn.close();
|
||||
listener.close();
|
||||
}
|
||||
|
||||
async function client() {
|
||||
const url = `http://${hostname}:${port}/`;
|
||||
const cmd = [
|
||||
"curl",
|
||||
"-I",
|
||||
"--request",
|
||||
"GET",
|
||||
"--url",
|
||||
url,
|
||||
"--header",
|
||||
"Accept-Encoding: gzip, deflate, br",
|
||||
];
|
||||
const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
|
||||
const status = await proc.status();
|
||||
assert(status.success);
|
||||
const output = decoder.decode(await proc.output());
|
||||
assert(output.includes("vary: Accept\r\n"));
|
||||
assert(!output.includes("content-encoding: "));
|
||||
proc.close();
|
||||
}
|
||||
|
||||
await Promise.all([server(), client()]);
|
||||
},
|
||||
});
|
||||
|
||||
function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader {
|
||||
// Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
|
||||
const tp = new TextProtoReader(r);
|
||||
|
|
|
@ -15,10 +15,15 @@ path = "lib.rs"
|
|||
|
||||
[dependencies]
|
||||
base64 = "0.13.0"
|
||||
brotli = "3.3.3"
|
||||
bytes = "1"
|
||||
cache_control = "0.2.0"
|
||||
deno_core = { version = "0.121.0", path = "../../core" }
|
||||
deno_websocket = { version = "0.44.0", path = "../websocket" }
|
||||
flate2 = "1.0.22"
|
||||
fly-accept-encoding = "0.2.0-alpha.5"
|
||||
hyper = { version = "0.14.9", features = ["server", "stream", "http1", "http2", "runtime"] }
|
||||
mime = "0.3.16"
|
||||
percent-encoding = "2.1.0"
|
||||
ring = "0.16.20"
|
||||
serde = { version = "1.0.129", features = ["derive"] }
|
||||
|
|
660
ext/http/compressible.rs
Normal file
660
ext/http/compressible.rs
Normal file
|
@ -0,0 +1,660 @@
|
|||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::ByteString;
|
||||
|
||||
// Data obtained from https://github.com/jshttp/mime-db/blob/fa5e4ef3cc8907ec3c5ec5b85af0c63d7059a5cd/db.json
|
||||
// Important! Keep this list sorted alphabetically.
|
||||
const CONTENT_TYPES: &[&str] = &[
|
||||
"application/3gpdash-qoe-report+xml",
|
||||
"application/3gpp-ims+xml",
|
||||
"application/3gpphal+json",
|
||||
"application/3gpphalforms+json",
|
||||
"application/activity+json",
|
||||
"application/alto-costmap+json",
|
||||
"application/alto-costmapfilter+json",
|
||||
"application/alto-directory+json",
|
||||
"application/alto-endpointcost+json",
|
||||
"application/alto-endpointcostparams+json",
|
||||
"application/alto-endpointprop+json",
|
||||
"application/alto-endpointpropparams+json",
|
||||
"application/alto-error+json",
|
||||
"application/alto-networkmap+json",
|
||||
"application/alto-networkmapfilter+json",
|
||||
"application/alto-updatestreamcontrol+json",
|
||||
"application/alto-updatestreamparams+json",
|
||||
"application/atom+xml",
|
||||
"application/atomcat+xml",
|
||||
"application/atomdeleted+xml",
|
||||
"application/atomsvc+xml",
|
||||
"application/atsc-dwd+xml",
|
||||
"application/atsc-held+xml",
|
||||
"application/atsc-rdt+json",
|
||||
"application/atsc-rsat+xml",
|
||||
"application/auth-policy+xml",
|
||||
"application/beep+xml",
|
||||
"application/calendar+json",
|
||||
"application/calendar+xml",
|
||||
"application/captive+json",
|
||||
"application/ccmp+xml",
|
||||
"application/ccxml+xml",
|
||||
"application/cdfx+xml",
|
||||
"application/cea-2018+xml",
|
||||
"application/cellml+xml",
|
||||
"application/clue+xml",
|
||||
"application/clue_info+xml",
|
||||
"application/cnrp+xml",
|
||||
"application/coap-group+json",
|
||||
"application/conference-info+xml",
|
||||
"application/cpl+xml",
|
||||
"application/csta+xml",
|
||||
"application/cstadata+xml",
|
||||
"application/csvm+json",
|
||||
"application/dart",
|
||||
"application/dash+xml",
|
||||
"application/davmount+xml",
|
||||
"application/dialog-info+xml",
|
||||
"application/dicom+json",
|
||||
"application/dicom+xml",
|
||||
"application/dns+json",
|
||||
"application/docbook+xml",
|
||||
"application/dskpp+xml",
|
||||
"application/dssc+xml",
|
||||
"application/ecmascript",
|
||||
"application/elm+json",
|
||||
"application/elm+xml",
|
||||
"application/emergencycalldata.cap+xml",
|
||||
"application/emergencycalldata.comment+xml",
|
||||
"application/emergencycalldata.control+xml",
|
||||
"application/emergencycalldata.deviceinfo+xml",
|
||||
"application/emergencycalldata.providerinfo+xml",
|
||||
"application/emergencycalldata.serviceinfo+xml",
|
||||
"application/emergencycalldata.subscriberinfo+xml",
|
||||
"application/emergencycalldata.veds+xml",
|
||||
"application/emma+xml",
|
||||
"application/emotionml+xml",
|
||||
"application/epp+xml",
|
||||
"application/expect-ct-report+json",
|
||||
"application/fdt+xml",
|
||||
"application/fhir+json",
|
||||
"application/fhir+xml",
|
||||
"application/fido.trusted-apps+json",
|
||||
"application/framework-attributes+xml",
|
||||
"application/geo+json",
|
||||
"application/geoxacml+xml",
|
||||
"application/gml+xml",
|
||||
"application/gpx+xml",
|
||||
"application/held+xml",
|
||||
"application/ibe-key-request+xml",
|
||||
"application/ibe-pkg-reply+xml",
|
||||
"application/im-iscomposing+xml",
|
||||
"application/inkml+xml",
|
||||
"application/its+xml",
|
||||
"application/javascript",
|
||||
"application/jf2feed+json",
|
||||
"application/jose+json",
|
||||
"application/jrd+json",
|
||||
"application/jscalendar+json",
|
||||
"application/json",
|
||||
"application/json-patch+json",
|
||||
"application/jsonml+json",
|
||||
"application/jwk+json",
|
||||
"application/jwk-set+json",
|
||||
"application/kpml-request+xml",
|
||||
"application/kpml-response+xml",
|
||||
"application/ld+json",
|
||||
"application/lgr+xml",
|
||||
"application/load-control+xml",
|
||||
"application/lost+xml",
|
||||
"application/lostsync+xml",
|
||||
"application/mads+xml",
|
||||
"application/manifest+json",
|
||||
"application/marcxml+xml",
|
||||
"application/mathml+xml",
|
||||
"application/mathml-content+xml",
|
||||
"application/mathml-presentation+xml",
|
||||
"application/mbms-associated-procedure-description+xml",
|
||||
"application/mbms-deregister+xml",
|
||||
"application/mbms-envelope+xml",
|
||||
"application/mbms-msk+xml",
|
||||
"application/mbms-msk-response+xml",
|
||||
"application/mbms-protection-description+xml",
|
||||
"application/mbms-reception-report+xml",
|
||||
"application/mbms-register+xml",
|
||||
"application/mbms-register-response+xml",
|
||||
"application/mbms-schedule+xml",
|
||||
"application/mbms-user-service-description+xml",
|
||||
"application/media-policy-dataset+xml",
|
||||
"application/media_control+xml",
|
||||
"application/mediaservercontrol+xml",
|
||||
"application/merge-patch+json",
|
||||
"application/metalink+xml",
|
||||
"application/metalink4+xml",
|
||||
"application/mets+xml",
|
||||
"application/mmt-aei+xml",
|
||||
"application/mmt-usd+xml",
|
||||
"application/mods+xml",
|
||||
"application/mrb-consumer+xml",
|
||||
"application/mrb-publish+xml",
|
||||
"application/msc-ivr+xml",
|
||||
"application/msc-mixer+xml",
|
||||
"application/mud+json",
|
||||
"application/nlsml+xml",
|
||||
"application/odm+xml",
|
||||
"application/oebps-package+xml",
|
||||
"application/omdoc+xml",
|
||||
"application/opc-nodeset+xml",
|
||||
"application/p2p-overlay+xml",
|
||||
"application/patch-ops-error+xml",
|
||||
"application/pidf+xml",
|
||||
"application/pidf-diff+xml",
|
||||
"application/pls+xml",
|
||||
"application/poc-settings+xml",
|
||||
"application/postscript",
|
||||
"application/ppsp-tracker+json",
|
||||
"application/problem+json",
|
||||
"application/problem+xml",
|
||||
"application/provenance+xml",
|
||||
"application/prs.xsf+xml",
|
||||
"application/pskc+xml",
|
||||
"application/pvd+json",
|
||||
"application/raml+yaml",
|
||||
"application/rdap+json",
|
||||
"application/rdf+xml",
|
||||
"application/reginfo+xml",
|
||||
"application/reputon+json",
|
||||
"application/resource-lists+xml",
|
||||
"application/resource-lists-diff+xml",
|
||||
"application/rfc+xml",
|
||||
"application/rlmi+xml",
|
||||
"application/rls-services+xml",
|
||||
"application/route-apd+xml",
|
||||
"application/route-s-tsid+xml",
|
||||
"application/route-usd+xml",
|
||||
"application/rsd+xml",
|
||||
"application/rss+xml",
|
||||
"application/rtf",
|
||||
"application/samlassertion+xml",
|
||||
"application/samlmetadata+xml",
|
||||
"application/sarif+json",
|
||||
"application/sarif-external-properties+json",
|
||||
"application/sbml+xml",
|
||||
"application/scaip+xml",
|
||||
"application/scim+json",
|
||||
"application/senml+json",
|
||||
"application/senml+xml",
|
||||
"application/senml-etch+json",
|
||||
"application/sensml+json",
|
||||
"application/sensml+xml",
|
||||
"application/sep+xml",
|
||||
"application/shf+xml",
|
||||
"application/simple-filter+xml",
|
||||
"application/smil+xml",
|
||||
"application/soap+xml",
|
||||
"application/sparql-results+xml",
|
||||
"application/spirits-event+xml",
|
||||
"application/srgs+xml",
|
||||
"application/sru+xml",
|
||||
"application/ssdl+xml",
|
||||
"application/ssml+xml",
|
||||
"application/stix+json",
|
||||
"application/swid+xml",
|
||||
"application/tar",
|
||||
"application/taxii+json",
|
||||
"application/td+json",
|
||||
"application/tei+xml",
|
||||
"application/thraud+xml",
|
||||
"application/tlsrpt+json",
|
||||
"application/toml",
|
||||
"application/ttml+xml",
|
||||
"application/urc-grpsheet+xml",
|
||||
"application/urc-ressheet+xml",
|
||||
"application/urc-targetdesc+xml",
|
||||
"application/urc-uisocketdesc+xml",
|
||||
"application/vcard+json",
|
||||
"application/vcard+xml",
|
||||
"application/vnd.1000minds.decision-model+xml",
|
||||
"application/vnd.3gpp-prose+xml",
|
||||
"application/vnd.3gpp-prose-pc3ch+xml",
|
||||
"application/vnd.3gpp.access-transfer-events+xml",
|
||||
"application/vnd.3gpp.bsf+xml",
|
||||
"application/vnd.3gpp.gmop+xml",
|
||||
"application/vnd.3gpp.mcdata-affiliation-command+xml",
|
||||
"application/vnd.3gpp.mcdata-info+xml",
|
||||
"application/vnd.3gpp.mcdata-service-config+xml",
|
||||
"application/vnd.3gpp.mcdata-ue-config+xml",
|
||||
"application/vnd.3gpp.mcdata-user-profile+xml",
|
||||
"application/vnd.3gpp.mcptt-affiliation-command+xml",
|
||||
"application/vnd.3gpp.mcptt-floor-request+xml",
|
||||
"application/vnd.3gpp.mcptt-info+xml",
|
||||
"application/vnd.3gpp.mcptt-location-info+xml",
|
||||
"application/vnd.3gpp.mcptt-mbms-usage-info+xml",
|
||||
"application/vnd.3gpp.mcptt-service-config+xml",
|
||||
"application/vnd.3gpp.mcptt-signed+xml",
|
||||
"application/vnd.3gpp.mcptt-ue-config+xml",
|
||||
"application/vnd.3gpp.mcptt-ue-init-config+xml",
|
||||
"application/vnd.3gpp.mcptt-user-profile+xml",
|
||||
"application/vnd.3gpp.mcvideo-affiliation-command+xml",
|
||||
"application/vnd.3gpp.mcvideo-affiliation-info+xml",
|
||||
"application/vnd.3gpp.mcvideo-info+xml",
|
||||
"application/vnd.3gpp.mcvideo-location-info+xml",
|
||||
"application/vnd.3gpp.mcvideo-mbms-usage-info+xml",
|
||||
"application/vnd.3gpp.mcvideo-service-config+xml",
|
||||
"application/vnd.3gpp.mcvideo-transmission-request+xml",
|
||||
"application/vnd.3gpp.mcvideo-ue-config+xml",
|
||||
"application/vnd.3gpp.mcvideo-user-profile+xml",
|
||||
"application/vnd.3gpp.mid-call+xml",
|
||||
"application/vnd.3gpp.sms+xml",
|
||||
"application/vnd.3gpp.srvcc-ext+xml",
|
||||
"application/vnd.3gpp.srvcc-info+xml",
|
||||
"application/vnd.3gpp.state-and-event-info+xml",
|
||||
"application/vnd.3gpp.ussd+xml",
|
||||
"application/vnd.3gpp2.bcmcsinfo+xml",
|
||||
"application/vnd.adobe.xdp+xml",
|
||||
"application/vnd.amadeus+json",
|
||||
"application/vnd.amundsen.maze+xml",
|
||||
"application/vnd.api+json",
|
||||
"application/vnd.aplextor.warrp+json",
|
||||
"application/vnd.apothekende.reservation+json",
|
||||
"application/vnd.apple.installer+xml",
|
||||
"application/vnd.artisan+json",
|
||||
"application/vnd.avalon+json",
|
||||
"application/vnd.avistar+xml",
|
||||
"application/vnd.balsamiq.bmml+xml",
|
||||
"application/vnd.bbf.usp.msg+json",
|
||||
"application/vnd.bekitzur-stech+json",
|
||||
"application/vnd.biopax.rdf+xml",
|
||||
"application/vnd.byu.uapi+json",
|
||||
"application/vnd.capasystems-pg+json",
|
||||
"application/vnd.chemdraw+xml",
|
||||
"application/vnd.citationstyles.style+xml",
|
||||
"application/vnd.collection+json",
|
||||
"application/vnd.collection.doc+json",
|
||||
"application/vnd.collection.next+json",
|
||||
"application/vnd.coreos.ignition+json",
|
||||
"application/vnd.criticaltools.wbs+xml",
|
||||
"application/vnd.cryptii.pipe+json",
|
||||
"application/vnd.ctct.ws+xml",
|
||||
"application/vnd.cyan.dean.root+xml",
|
||||
"application/vnd.cyclonedx+json",
|
||||
"application/vnd.cyclonedx+xml",
|
||||
"application/vnd.dart",
|
||||
"application/vnd.datapackage+json",
|
||||
"application/vnd.dataresource+json",
|
||||
"application/vnd.dece.ttml+xml",
|
||||
"application/vnd.dm.delegation+xml",
|
||||
"application/vnd.document+json",
|
||||
"application/vnd.drive+json",
|
||||
"application/vnd.dvb.dvbisl+xml",
|
||||
"application/vnd.dvb.notif-aggregate-root+xml",
|
||||
"application/vnd.dvb.notif-container+xml",
|
||||
"application/vnd.dvb.notif-generic+xml",
|
||||
"application/vnd.dvb.notif-ia-msglist+xml",
|
||||
"application/vnd.dvb.notif-ia-registration-request+xml",
|
||||
"application/vnd.dvb.notif-ia-registration-response+xml",
|
||||
"application/vnd.dvb.notif-init+xml",
|
||||
"application/vnd.emclient.accessrequest+xml",
|
||||
"application/vnd.eprints.data+xml",
|
||||
"application/vnd.eszigno3+xml",
|
||||
"application/vnd.etsi.aoc+xml",
|
||||
"application/vnd.etsi.cug+xml",
|
||||
"application/vnd.etsi.iptvcommand+xml",
|
||||
"application/vnd.etsi.iptvdiscovery+xml",
|
||||
"application/vnd.etsi.iptvprofile+xml",
|
||||
"application/vnd.etsi.iptvsad-bc+xml",
|
||||
"application/vnd.etsi.iptvsad-cod+xml",
|
||||
"application/vnd.etsi.iptvsad-npvr+xml",
|
||||
"application/vnd.etsi.iptvservice+xml",
|
||||
"application/vnd.etsi.iptvsync+xml",
|
||||
"application/vnd.etsi.iptvueprofile+xml",
|
||||
"application/vnd.etsi.mcid+xml",
|
||||
"application/vnd.etsi.overload-control-policy-dataset+xml",
|
||||
"application/vnd.etsi.pstn+xml",
|
||||
"application/vnd.etsi.sci+xml",
|
||||
"application/vnd.etsi.simservs+xml",
|
||||
"application/vnd.etsi.tsl+xml",
|
||||
"application/vnd.fujifilm.fb.jfi+xml",
|
||||
"application/vnd.futoin+json",
|
||||
"application/vnd.gentics.grd+json",
|
||||
"application/vnd.geo+json",
|
||||
"application/vnd.geocube+xml",
|
||||
"application/vnd.google-earth.kml+xml",
|
||||
"application/vnd.gov.sk.e-form+xml",
|
||||
"application/vnd.gov.sk.xmldatacontainer+xml",
|
||||
"application/vnd.hal+json",
|
||||
"application/vnd.hal+xml",
|
||||
"application/vnd.handheld-entertainment+xml",
|
||||
"application/vnd.hc+json",
|
||||
"application/vnd.heroku+json",
|
||||
"application/vnd.hyper+json",
|
||||
"application/vnd.hyper-item+json",
|
||||
"application/vnd.hyperdrive+json",
|
||||
"application/vnd.ims.lis.v2.result+json",
|
||||
"application/vnd.ims.lti.v2.toolconsumerprofile+json",
|
||||
"application/vnd.ims.lti.v2.toolproxy+json",
|
||||
"application/vnd.ims.lti.v2.toolproxy.id+json",
|
||||
"application/vnd.ims.lti.v2.toolsettings+json",
|
||||
"application/vnd.ims.lti.v2.toolsettings.simple+json",
|
||||
"application/vnd.informedcontrol.rms+xml",
|
||||
"application/vnd.infotech.project+xml",
|
||||
"application/vnd.iptc.g2.catalogitem+xml",
|
||||
"application/vnd.iptc.g2.conceptitem+xml",
|
||||
"application/vnd.iptc.g2.knowledgeitem+xml",
|
||||
"application/vnd.iptc.g2.newsitem+xml",
|
||||
"application/vnd.iptc.g2.newsmessage+xml",
|
||||
"application/vnd.iptc.g2.packageitem+xml",
|
||||
"application/vnd.iptc.g2.planningitem+xml",
|
||||
"application/vnd.irepository.package+xml",
|
||||
"application/vnd.las.las+json",
|
||||
"application/vnd.las.las+xml",
|
||||
"application/vnd.leap+json",
|
||||
"application/vnd.liberty-request+xml",
|
||||
"application/vnd.llamagraphics.life-balance.exchange+xml",
|
||||
"application/vnd.marlin.drm.actiontoken+xml",
|
||||
"application/vnd.marlin.drm.conftoken+xml",
|
||||
"application/vnd.marlin.drm.license+xml",
|
||||
"application/vnd.mason+json",
|
||||
"application/vnd.micro+json",
|
||||
"application/vnd.miele+json",
|
||||
"application/vnd.mozilla.xul+xml",
|
||||
"application/vnd.ms-fontobject",
|
||||
"application/vnd.ms-office.activex+xml",
|
||||
"application/vnd.ms-opentype",
|
||||
"application/vnd.ms-playready.initiator+xml",
|
||||
"application/vnd.ms-printdevicecapabilities+xml",
|
||||
"application/vnd.ms-printing.printticket+xml",
|
||||
"application/vnd.ms-printschematicket+xml",
|
||||
"application/vnd.nearst.inv+json",
|
||||
"application/vnd.nokia.conml+xml",
|
||||
"application/vnd.nokia.iptv.config+xml",
|
||||
"application/vnd.nokia.landmark+xml",
|
||||
"application/vnd.nokia.landmarkcollection+xml",
|
||||
"application/vnd.nokia.n-gage.ac+xml",
|
||||
"application/vnd.nokia.pcd+xml",
|
||||
"application/vnd.oci.image.manifest.v1+json",
|
||||
"application/vnd.oftn.l10n+json",
|
||||
"application/vnd.oipf.contentaccessdownload+xml",
|
||||
"application/vnd.oipf.contentaccessstreaming+xml",
|
||||
"application/vnd.oipf.dae.svg+xml",
|
||||
"application/vnd.oipf.dae.xhtml+xml",
|
||||
"application/vnd.oipf.mippvcontrolmessage+xml",
|
||||
"application/vnd.oipf.spdiscovery+xml",
|
||||
"application/vnd.oipf.spdlist+xml",
|
||||
"application/vnd.oipf.ueprofile+xml",
|
||||
"application/vnd.oipf.userprofile+xml",
|
||||
"application/vnd.oma.bcast.associated-procedure-parameter+xml",
|
||||
"application/vnd.oma.bcast.drm-trigger+xml",
|
||||
"application/vnd.oma.bcast.imd+xml",
|
||||
"application/vnd.oma.bcast.notification+xml",
|
||||
"application/vnd.oma.bcast.sgdd+xml",
|
||||
"application/vnd.oma.bcast.smartcard-trigger+xml",
|
||||
"application/vnd.oma.bcast.sprov+xml",
|
||||
"application/vnd.oma.cab-address-book+xml",
|
||||
"application/vnd.oma.cab-feature-handler+xml",
|
||||
"application/vnd.oma.cab-pcc+xml",
|
||||
"application/vnd.oma.cab-subs-invite+xml",
|
||||
"application/vnd.oma.cab-user-prefs+xml",
|
||||
"application/vnd.oma.dd2+xml",
|
||||
"application/vnd.oma.drm.risd+xml",
|
||||
"application/vnd.oma.group-usage-list+xml",
|
||||
"application/vnd.oma.lwm2m+json",
|
||||
"application/vnd.oma.pal+xml",
|
||||
"application/vnd.oma.poc.detailed-progress-report+xml",
|
||||
"application/vnd.oma.poc.final-report+xml",
|
||||
"application/vnd.oma.poc.groups+xml",
|
||||
"application/vnd.oma.poc.invocation-descriptor+xml",
|
||||
"application/vnd.oma.poc.optimized-progress-report+xml",
|
||||
"application/vnd.oma.scidm.messages+xml",
|
||||
"application/vnd.oma.xcap-directory+xml",
|
||||
"application/vnd.omads-email+xml",
|
||||
"application/vnd.omads-file+xml",
|
||||
"application/vnd.omads-folder+xml",
|
||||
"application/vnd.openblox.game+xml",
|
||||
"application/vnd.openstreetmap.data+xml",
|
||||
"application/vnd.openxmlformats-officedocument.custom-properties+xml",
|
||||
"application/vnd.openxmlformats-officedocument.customxmlproperties+xml",
|
||||
"application/vnd.openxmlformats-officedocument.drawing+xml",
|
||||
"application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
|
||||
"application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml",
|
||||
"application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml",
|
||||
"application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml",
|
||||
"application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml",
|
||||
"application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml",
|
||||
"application/vnd.openxmlformats-officedocument.extended-properties+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.comments+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presprops+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.slide+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.tags+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.template.main+xml",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
|
||||
"application/vnd.openxmlformats-officedocument.theme+xml",
|
||||
"application/vnd.openxmlformats-officedocument.themeoverride+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml",
|
||||
"application/vnd.openxmlformats-package.core-properties+xml",
|
||||
"application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml",
|
||||
"application/vnd.openxmlformats-package.relationships+xml",
|
||||
"application/vnd.oracle.resource+json",
|
||||
"application/vnd.otps.ct-kip+xml",
|
||||
"application/vnd.pagerduty+json",
|
||||
"application/vnd.poc.group-advertisement+xml",
|
||||
"application/vnd.pwg-xhtml-print+xml",
|
||||
"application/vnd.radisys.moml+xml",
|
||||
"application/vnd.radisys.msml+xml",
|
||||
"application/vnd.radisys.msml-audit+xml",
|
||||
"application/vnd.radisys.msml-audit-conf+xml",
|
||||
"application/vnd.radisys.msml-audit-conn+xml",
|
||||
"application/vnd.radisys.msml-audit-dialog+xml",
|
||||
"application/vnd.radisys.msml-audit-stream+xml",
|
||||
"application/vnd.radisys.msml-conf+xml",
|
||||
"application/vnd.radisys.msml-dialog+xml",
|
||||
"application/vnd.radisys.msml-dialog-base+xml",
|
||||
"application/vnd.radisys.msml-dialog-fax-detect+xml",
|
||||
"application/vnd.radisys.msml-dialog-fax-sendrecv+xml",
|
||||
"application/vnd.radisys.msml-dialog-group+xml",
|
||||
"application/vnd.radisys.msml-dialog-speech+xml",
|
||||
"application/vnd.radisys.msml-dialog-transform+xml",
|
||||
"application/vnd.recordare.musicxml+xml",
|
||||
"application/vnd.restful+json",
|
||||
"application/vnd.route66.link66+xml",
|
||||
"application/vnd.seis+json",
|
||||
"application/vnd.shootproof+json",
|
||||
"application/vnd.shopkick+json",
|
||||
"application/vnd.siren+json",
|
||||
"application/vnd.software602.filler.form+xml",
|
||||
"application/vnd.solent.sdkm+xml",
|
||||
"application/vnd.sun.wadl+xml",
|
||||
"application/vnd.sycle+xml",
|
||||
"application/vnd.syncml+xml",
|
||||
"application/vnd.syncml.dm+xml",
|
||||
"application/vnd.syncml.dmddf+xml",
|
||||
"application/vnd.syncml.dmtnds+xml",
|
||||
"application/vnd.tableschema+json",
|
||||
"application/vnd.think-cell.ppttc+json",
|
||||
"application/vnd.tmd.mediaflex.api+xml",
|
||||
"application/vnd.uoml+xml",
|
||||
"application/vnd.vel+json",
|
||||
"application/vnd.wv.csp+xml",
|
||||
"application/vnd.wv.ssp+xml",
|
||||
"application/vnd.xacml+json",
|
||||
"application/vnd.xmi+xml",
|
||||
"application/vnd.yamaha.openscoreformat.osfpvg+xml",
|
||||
"application/vnd.zzazz.deck+xml",
|
||||
"application/voicexml+xml",
|
||||
"application/voucher-cms+json",
|
||||
"application/wasm",
|
||||
"application/watcherinfo+xml",
|
||||
"application/webpush-options+json",
|
||||
"application/wsdl+xml",
|
||||
"application/wspolicy+xml",
|
||||
"application/x-dtbncx+xml",
|
||||
"application/x-dtbook+xml",
|
||||
"application/x-dtbresource+xml",
|
||||
"application/x-httpd-php",
|
||||
"application/x-javascript",
|
||||
"application/x-ns-proxy-autoconfig",
|
||||
"application/x-sh",
|
||||
"application/x-tar",
|
||||
"application/x-virtualbox-hdd",
|
||||
"application/x-virtualbox-ova",
|
||||
"application/x-virtualbox-ovf",
|
||||
"application/x-virtualbox-vbox",
|
||||
"application/x-virtualbox-vdi",
|
||||
"application/x-virtualbox-vhd",
|
||||
"application/x-virtualbox-vmdk",
|
||||
"application/x-web-app-manifest+json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"application/x-xliff+xml",
|
||||
"application/xacml+xml",
|
||||
"application/xaml+xml",
|
||||
"application/xcap-att+xml",
|
||||
"application/xcap-caps+xml",
|
||||
"application/xcap-diff+xml",
|
||||
"application/xcap-el+xml",
|
||||
"application/xcap-error+xml",
|
||||
"application/xcap-ns+xml",
|
||||
"application/xcon-conference-info+xml",
|
||||
"application/xcon-conference-info-diff+xml",
|
||||
"application/xenc+xml",
|
||||
"application/xhtml+xml",
|
||||
"application/xhtml-voice+xml",
|
||||
"application/xliff+xml",
|
||||
"application/xml",
|
||||
"application/xml-dtd",
|
||||
"application/xml-patch+xml",
|
||||
"application/xmpp+xml",
|
||||
"application/xop+xml",
|
||||
"application/xproc+xml",
|
||||
"application/xslt+xml",
|
||||
"application/xspf+xml",
|
||||
"application/xv+xml",
|
||||
"application/yang-data+json",
|
||||
"application/yang-data+xml",
|
||||
"application/yang-patch+json",
|
||||
"application/yang-patch+xml",
|
||||
"application/yin+xml",
|
||||
"font/otf",
|
||||
"font/ttf",
|
||||
"image/bmp",
|
||||
"image/svg+xml",
|
||||
"image/vnd.adobe.photoshop",
|
||||
"image/x-icon",
|
||||
"image/x-ms-bmp",
|
||||
"message/imdn+xml",
|
||||
"message/rfc822",
|
||||
"model/gltf+json",
|
||||
"model/gltf-binary",
|
||||
"model/vnd.collada+xml",
|
||||
"model/vnd.moml+xml",
|
||||
"model/x3d+xml",
|
||||
"text/cache-manifest",
|
||||
"text/calender",
|
||||
"text/cmd",
|
||||
"text/css",
|
||||
"text/csv",
|
||||
"text/html",
|
||||
"text/javascript",
|
||||
"text/jsx",
|
||||
"text/less",
|
||||
"text/markdown",
|
||||
"text/mdx",
|
||||
"text/n3",
|
||||
"text/plain",
|
||||
"text/richtext",
|
||||
"text/rtf",
|
||||
"text/tab-separated-values",
|
||||
"text/uri-list",
|
||||
"text/vcard",
|
||||
"text/vtt",
|
||||
"text/x-gwt-rpc",
|
||||
"text/x-jquery-tmpl",
|
||||
"text/x-markdown",
|
||||
"text/x-org",
|
||||
"text/x-processing",
|
||||
"text/x-suse-ymp",
|
||||
"text/xml",
|
||||
"text/yaml",
|
||||
"x-shader/x-fragment",
|
||||
"x-shader/x-vertex",
|
||||
];
|
||||
|
||||
/// Determine if the supplied content type is considered compressible
|
||||
pub fn is_content_compressible(
|
||||
maybe_content_type: Option<&ByteString>,
|
||||
) -> bool {
|
||||
if let Some(content_type) = maybe_content_type {
|
||||
if let Ok(content_type) = std::str::from_utf8(content_type.as_ref()) {
|
||||
if let Ok(content_type) = content_type.parse::<mime::Mime>() {
|
||||
return CONTENT_TYPES
|
||||
.binary_search(&content_type.essence_str())
|
||||
.is_ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn content_type_none() {
|
||||
assert!(!is_content_compressible(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_compressible_content_type() {
|
||||
assert!(!is_content_compressible(Some(&ByteString(
|
||||
b"application/vnd.deno+json".to_vec()
|
||||
))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ncompressible_content_type() {
|
||||
assert!(is_content_compressible(Some(&ByteString(
|
||||
b"application/json".to_vec()
|
||||
))));
|
||||
}
|
||||
}
|
161
ext/http/lib.rs
161
ext/http/lib.rs
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use bytes::Bytes;
|
||||
use cache_control::CacheControl;
|
||||
use deno_core::error::custom_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::channel::mpsc;
|
||||
|
@ -34,6 +35,9 @@ use deno_core::ResourceId;
|
|||
use deno_core::StringOrBuffer;
|
||||
use deno_core::ZeroCopyBuf;
|
||||
use deno_websocket::ws_create_server_stream;
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression;
|
||||
use fly_accept_encoding::Encoding;
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::Service;
|
||||
use hyper::Body;
|
||||
|
@ -47,6 +51,7 @@ use std::cmp::min;
|
|||
use std::error::Error;
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::mem::replace;
|
||||
use std::mem::take;
|
||||
use std::pin::Pin;
|
||||
|
@ -58,6 +63,8 @@ use tokio::io::AsyncRead;
|
|||
use tokio::io::AsyncWrite;
|
||||
use tokio::task::spawn_local;
|
||||
|
||||
mod compressible;
|
||||
|
||||
pub fn init() -> Extension {
|
||||
Extension::builder()
|
||||
.js(include_js_files!(
|
||||
|
@ -292,6 +299,7 @@ struct HttpStreamResource {
|
|||
conn: Rc<HttpConnResource>,
|
||||
rd: AsyncRefCell<HttpRequestReader>,
|
||||
wr: AsyncRefCell<HttpResponseWriter>,
|
||||
accept_encoding: RefCell<Encoding>,
|
||||
cancel_handle: CancelHandle,
|
||||
}
|
||||
|
||||
|
@ -305,6 +313,7 @@ impl HttpStreamResource {
|
|||
conn: conn.clone(),
|
||||
rd: HttpRequestReader::Headers(request).into(),
|
||||
wr: HttpResponseWriter::Headers(response_tx).into(),
|
||||
accept_encoding: RefCell::new(Encoding::Identity),
|
||||
cancel_handle: CancelHandle::new(),
|
||||
}
|
||||
}
|
||||
|
@ -381,6 +390,14 @@ async fn op_http_accept(
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
{
|
||||
let mut accept_encoding = stream.accept_encoding.borrow_mut();
|
||||
*accept_encoding = fly_accept_encoding::parse(request.headers())
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(Encoding::Identity);
|
||||
}
|
||||
|
||||
let method = request.method().to_string();
|
||||
let headers = req_headers(request);
|
||||
let url = req_url(request, conn.scheme(), conn.addr());
|
||||
|
@ -497,22 +514,164 @@ async fn op_http_write_headers(
|
|||
|
||||
let mut builder = Response::builder().status(status);
|
||||
|
||||
let mut body_compressible = false;
|
||||
let mut headers_allow_compression = true;
|
||||
let mut vary_header = None;
|
||||
let mut etag_header = None;
|
||||
let mut content_type_header = None;
|
||||
|
||||
builder.headers_mut().unwrap().reserve(headers.len());
|
||||
for (key, value) in &headers {
|
||||
match &*key.to_ascii_lowercase() {
|
||||
b"cache-control" => {
|
||||
if let Ok(value) = std::str::from_utf8(value) {
|
||||
if let Some(cache_control) = CacheControl::from_value(value) {
|
||||
// We skip compression if the cache-control header value is set to
|
||||
// "no-transform"
|
||||
if cache_control.no_transform {
|
||||
headers_allow_compression = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
headers_allow_compression = false;
|
||||
}
|
||||
}
|
||||
b"content-range" => {
|
||||
// we skip compression if the `content-range` header value is set, as it
|
||||
// indicates the contents of the body were negotiated based directly
|
||||
// with the user code and we can't compress the response
|
||||
headers_allow_compression = false;
|
||||
}
|
||||
b"content-type" => {
|
||||
if !value.is_empty() {
|
||||
content_type_header = Some(value);
|
||||
}
|
||||
}
|
||||
b"content-encoding" => {
|
||||
// we don't compress if a content-encoding header was provided
|
||||
headers_allow_compression = false;
|
||||
}
|
||||
// we store the values of ETag and Vary and skip adding them for now, as
|
||||
// we may need to modify or change.
|
||||
b"etag" => {
|
||||
if !value.is_empty() {
|
||||
etag_header = Some(value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
b"vary" => {
|
||||
if !value.is_empty() {
|
||||
vary_header = Some(value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
builder = builder.header(key.as_ref(), value.as_ref());
|
||||
}
|
||||
|
||||
if headers_allow_compression {
|
||||
body_compressible =
|
||||
compressible::is_content_compressible(content_type_header);
|
||||
}
|
||||
|
||||
let body: Response<Body>;
|
||||
let new_wr: HttpResponseWriter;
|
||||
|
||||
match data {
|
||||
Some(data) => {
|
||||
// If a buffer was passed, we use it to construct a response body.
|
||||
// Set Vary: Accept-Encoding header for direct body response.
|
||||
// Note: we set the header irrespective of whether or not we compress the
|
||||
// data to make sure cache services do not serve uncompressed data to
|
||||
// clients that support compression.
|
||||
let vary_value = if let Some(value) = vary_header {
|
||||
if let Ok(value_str) = std::str::from_utf8(value.as_ref()) {
|
||||
if !value_str.to_lowercase().contains("accept-encoding") {
|
||||
format!("Accept-Encoding, {}", value_str)
|
||||
} else {
|
||||
value_str.to_string()
|
||||
}
|
||||
} else {
|
||||
// the header value wasn't valid UTF8, so it would have been a
|
||||
// problem anyways, so sending a default header.
|
||||
"Accept-Encoding".to_string()
|
||||
}
|
||||
} else {
|
||||
"Accept-Encoding".to_string()
|
||||
};
|
||||
builder = builder.header("vary", &vary_value);
|
||||
|
||||
let accepts_compression = matches!(
|
||||
*stream.accept_encoding.borrow(),
|
||||
Encoding::Brotli | Encoding::Gzip
|
||||
);
|
||||
|
||||
let should_compress =
|
||||
body_compressible && data.len() > 20 && accepts_compression;
|
||||
|
||||
if should_compress {
|
||||
// If user provided a ETag header for uncompressed data, we need to
|
||||
// ensure it is a Weak Etag header ("W/").
|
||||
if let Some(value) = etag_header {
|
||||
if let Ok(value_str) = std::str::from_utf8(value.as_ref()) {
|
||||
if !value_str.starts_with("W/") {
|
||||
builder = builder.header("etag", format!("W/{}", value_str));
|
||||
} else {
|
||||
builder = builder.header("etag", value.as_ref());
|
||||
}
|
||||
} else {
|
||||
builder = builder.header("etag", value.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
match *stream.accept_encoding.borrow() {
|
||||
Encoding::Brotli => {
|
||||
builder = builder.header("content-encoding", "br");
|
||||
// quality level 6 is based on google's nginx default value for
|
||||
// on-the-fly compression
|
||||
// https://github.com/google/ngx_brotli#brotli_comp_level
|
||||
// lgwin 22 is equivalent to brotli window size of (2**22)-16 bytes
|
||||
// (~4MB)
|
||||
let mut writer =
|
||||
brotli::CompressorWriter::new(Vec::new(), 4096, 6, 22);
|
||||
writer.write_all(&data.into_bytes())?;
|
||||
body = builder.body(writer.into_inner().into())?;
|
||||
}
|
||||
_ => {
|
||||
assert_eq!(*stream.accept_encoding.borrow(), Encoding::Gzip);
|
||||
builder = builder.header("content-encoding", "gzip");
|
||||
// Gzip, after level 1, doesn't produce significant size difference.
|
||||
// Probably the reason why nginx's default gzip compression level is
|
||||
// 1.
|
||||
// https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_comp_level
|
||||
let mut writer = GzEncoder::new(Vec::new(), Compression::new(1));
|
||||
writer.write_all(&data.into_bytes())?;
|
||||
body = builder.body(writer.finish().unwrap().into())?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(value) = etag_header {
|
||||
builder = builder.header("etag", value.as_ref());
|
||||
}
|
||||
// If a buffer was passed, but isn't compressible, we use it to
|
||||
// construct a response body.
|
||||
body = builder.body(data.into_bytes().into())?;
|
||||
}
|
||||
new_wr = HttpResponseWriter::Closed;
|
||||
}
|
||||
None => {
|
||||
// If no buffer was passed, the caller will stream the response body.
|
||||
|
||||
// TODO(@kitsonk) had compression for streamed bodies.
|
||||
|
||||
// Set the user provided ETag & Vary headers for a streaming response
|
||||
if let Some(value) = etag_header {
|
||||
builder = builder.header("etag", value.as_ref());
|
||||
}
|
||||
if let Some(value) = vary_header {
|
||||
builder = builder.header("vary", value.as_ref());
|
||||
}
|
||||
|
||||
let (body_tx, body_rx) = Body::channel();
|
||||
body = builder.body(body_rx)?;
|
||||
new_wr = HttpResponseWriter::Body(body_tx);
|
||||
|
|
Loading…
Reference in a new issue