-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ctx.Accepts() quality values and specificity #2387
Comments
Need to checkout express and other frameworks handling of q value and specificity, the code to solve this that immediately comes to mind will have impacts on performance and memory use. Want to make sure🧃worth🪗 |
yes will definitely have a performance impact then you go through the header values and not the arguments |
we could also split the header string and sort it so that we achieve the prioritization, but then we have one more allocation due to the created slice but maybe this can be done with byte buffer pool or it is not important because it is only a temporary allocation |
ref x/net/http discussion on content negotiation golang/go#19307 |
ref mdn docs on content negotiation https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation |
ref RFC 9110 HTTP Semantics 12.3. Request Content Negotiation |
Note https://www.rfc-editor.org/rfc/rfc9110.html#field.accept which shows:
So that means we should prioritize sort by q value, and that the header order only matters if q is the same, or is unspecified in which case it is assumed to be 1.0 It also goes on to talk about specificity. |
I'm working on fully cloning the express Accepts function, however I am seeing a behaviour difference from the assertions in Test_Ctx_Accepts in Fiber vs Express, specifically: c.Request().Header.Set(HeaderAccept, "text/html, application/json")
utils.AssertEqual(t, "text/*", c.Accepts("text/*")) Results in: --- FAIL: Test_Ctx_Accepts (0.01s)
/Users/sixcolors/Documents/GitHub/fiber/ctx_test.go:58:
Test: Test_Ctx_Accepts
Trace: ctx_test.go:58
Expect: text/* (string)
Result: (string)
FAIL
FAIL github.com/gofiber/fiber/v2 0.029s
FAIL The express / js function in the following sample const express = require('express');
var accepts = require('accepts')
var Negotiator = require('negotiator')
const app = express();
const port = 3000;
const availableMediaTypes = ['text/*'];
app.use(express.json());
app.get('/', (req, res) => {
negotiator = new Negotiator(req);
console.log(negotiator.mediaTypes(availableMediaTypes));
var accept = accepts(req)
console.log(accept.types(availableMediaTypes) ? 'accepts' : 'does not accept');
req.accepts(availableMediaTypes) ? console.log('express accepts') : console.log('express does not accept');
res.send('Hello, world!');
});
app.post('/data', (req, res) => {
if (req.accepts('json')) {
res.json({ message: 'Received JSON data' });
} else if (req.accepts('xml')) {
res.send('<message>Received XML data</message>');
} else {
res.status(406).send('Not Acceptable');
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
}); curl -v -H'Accept: text/html, application/json' http://localhost:3000/ Gives the console output: []
does not accept
express does not accept |
Working on code pre-PR on this branch: https://github.com/sixcolors/fiber/tree/2387-ctx-accepts-quality-values-and-specificity internal/negotiator package inspired by https://github.com/jshttp/negotiator which is what backs accepts, which is what express uses. Initial Benchmark_Ctx_Accepts resultsmaster:goos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 4540623 258.9 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 4152066 288.5 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 5991992 201.4 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/gofiber/fiber/v2 4.385s
goos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 4539039 265.7 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 4421416 271.6 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 6057902 200.2 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/gofiber/fiber/v2 4.404s 2387-ctx-accepts-quality-values-and-specificitygoos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 224628 5087 ns/op 2136 B/op 48 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 188163 6302 ns/op 2632 B/op 60 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 196066 6226 ns/op 2632 B/op 60 allocs/op
PASS
ok github.com/gofiber/fiber/v2 3.771s
goos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 228782 5086 ns/op 2136 B/op 48 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 186030 6339 ns/op 2632 B/op 60 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 185474 6242 ns/op 2632 B/op 60 allocs/op
PASS
ok github.com/gofiber/fiber/v2 3.724s |
First round of improving Benchmark_Ctx_Accepts results 2387-ctx-accepts-quality-values-and-specificitygoos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 297885 3920 ns/op 1608 B/op 36 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 215049 5191 ns/op 2104 B/op 48 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 231255 5119 ns/op 2104 B/op 48 allocs/op
PASS
ok github.com/gofiber/fiber/v2 3.658s
goos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 293320 3920 ns/op 1608 B/op 36 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 222104 5221 ns/op 2104 B/op 48 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 233214 5167 ns/op 2104 B/op 48 allocs/op
PASS
ok github.com/gofiber/fiber/v2 3.703s |
Second round of improving Benchmark_Ctx_Accepts results 2387-ctx-accepts-quality-values-and-specificitygoos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 316141 3569 ns/op 1472 B/op 33 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 254371 4868 ns/op 1968 B/op 45 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 242386 4798 ns/op 1968 B/op 45 allocs/op
PASS
ok github.com/gofiber/fiber/v2 3.705s
goos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 324574 3570 ns/op 1472 B/op 33 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 248742 4840 ns/op 1968 B/op 45 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 244814 4759 ns/op 1968 B/op 45 allocs/op
PASS
ok github.com/gofiber/fiber/v2 3.703s |
Okay I modified helpers.go getOffer to consider q values and specificity, it's much faster than my port of jshttp/negotiator branch: https://github.com/sixcolors/fiber/tree/2387-ctx-accepts-get-offer-q-and-specificity Benchmark_Ctx_Accepts resultsmastergoos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts
Benchmark_Ctx_Accepts/run-[]string{".xml"}
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 4718739 248.1 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 4823197 247.4 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 4701520 246.7 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 4730023 246.4 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 4619944 258.9 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 4630816 259.1 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 4634599 259.9 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 4590058 260.3 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 6283534 193.6 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 6131330 193.5 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 6143854 193.3 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 6021658 197.3 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsCharsets
Benchmark_Ctx_AcceptsCharsets-24 13285575 89.29 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsCharsets-24 12894056 88.99 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsCharsets-24 13402732 89.01 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsCharsets-24 13217695 89.30 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsEncodings
Benchmark_Ctx_AcceptsEncodings-24 10083732 117.9 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsEncodings-24 10080361 117.8 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsEncodings-24 10078560 118.9 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsEncodings-24 10137867 117.3 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsLanguages
Benchmark_Ctx_AcceptsLanguages-24 13242565 88.78 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsLanguages-24 13228040 89.09 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsLanguages-24 13319239 88.50 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsLanguages-24 13211436 88.96 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/gofiber/fiber/v2 32.674s 2387-ctx-accepts-get-offer-q-and-specificitygoarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts
Benchmark_Ctx_Accepts/run-[]string{".xml"}
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 1523955 781.3 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 1509940 783.0 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 1535641 786.1 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 1518062 785.9 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 1320118 911.8 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 1326496 904.1 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 1322679 902.0 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 1332966 905.7 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 1703186 704.0 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 1700726 703.4 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 1697983 724.6 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 1703149 715.7 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_AcceptsCharsets
Benchmark_Ctx_AcceptsCharsets-24 2777720 427.8 ns/op 96 B/op 2 allocs/op
Benchmark_Ctx_AcceptsCharsets-24 2841889 422.0 ns/op 96 B/op 2 allocs/op
Benchmark_Ctx_AcceptsCharsets-24 2825397 423.1 ns/op 96 B/op 2 allocs/op
Benchmark_Ctx_AcceptsCharsets-24 2842117 424.3 ns/op 96 B/op 2 allocs/op
Benchmark_Ctx_AcceptsEncodings
Benchmark_Ctx_AcceptsEncodings-24 1747647 686.6 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_AcceptsEncodings-24 1748542 680.2 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_AcceptsEncodings-24 1748302 687.9 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_AcceptsEncodings-24 1758098 685.1 ns/op 224 B/op 3 allocs/op
Benchmark_Ctx_AcceptsLanguages
Benchmark_Ctx_AcceptsLanguages-24 1000000 1065 ns/op 480 B/op 4 allocs/op
Benchmark_Ctx_AcceptsLanguages-24 1000000 1066 ns/op 480 B/op 4 allocs/op
Benchmark_Ctx_AcceptsLanguages-24 1000000 1061 ns/op 480 B/op 4 allocs/op
Benchmark_Ctx_AcceptsLanguages-24 1000000 1062 ns/op 480 B/op 4 allocs/op
PASS
ok github.com/gofiber/fiber/v2 42.648s |
I have a solution in my mind with the current getOffer method without allocations. |
Actually, I thought of a way to do it.... zero allocations: 2387-ctx-accepts-get-offer-q-and-specificityedited: I further optimized the sort algo goos: darwin
goarch: amd64
pkg: github.com/gofiber/fiber/v2
cpu: Intel(R) Xeon(R) CPU X5675 @ 3.07GHz
Benchmark_Ctx_Accepts
Benchmark_Ctx_Accepts/run-[]string{".xml"}
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 2682642 444.9 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 2776354 441.5 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 2707746 442.8 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{".xml"}-24 2711558 435.8 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 2141143 565.3 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 2128267 559.7 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 2091757 567.7 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"json",_"xml"}-24 2131636 559.6 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 3336694 360.9 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 3337960 359.7 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 3291346 362.4 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_Accepts/run-[]string{"application/json",_"application/xml"}-24 3306898 359.3 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsCharsets
Benchmark_Ctx_AcceptsCharsets-24 5187949 226.1 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsCharsets-24 5308161 227.7 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsCharsets-24 5307020 228.8 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsCharsets-24 5356268 225.0 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsEncodings
Benchmark_Ctx_AcceptsEncodings-24 3701896 326.2 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsEncodings-24 3664098 326.1 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsEncodings-24 3681938 325.8 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsEncodings-24 3667440 326.8 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsLanguages
Benchmark_Ctx_AcceptsLanguages-24 2441028 488.1 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsLanguages-24 2457464 488.6 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsLanguages-24 2418184 488.0 ns/op 0 B/op 0 allocs/op
Benchmark_Ctx_AcceptsLanguages-24 2451204 489.4 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/gofiber/fiber/v2 38.648s |
But still much slower in some cases. |
True, however, now the function follows the spec for Content Negotiation Fields. I'll also look at those cases where it's much slower to see what I can do for them. I'm not 100% sure I can make them as fast as they were while still being 'correct'; the benchmarks are an almost ideal case for the old implementation. Open to any help/suggestions you may have. |
I should say it follows more of the spec, as media range parameters aren't supported in the master or #2486 implementations. eg Accept: text/*, text/plain, text/plain;format=flowed, */*
text/plain;format=flowed
text/plain
text/*
*/* The ctx.Accepts() func does not provide for checking parameters. Doing so would likely incur undesirable performance and memory penalties. Thoughts? |
This issue is a follow on to #2386 and #2383
@ReneWerner87 good job, this make the existing test cases behave as expected.
Reading the ctx.Accepts docs my understanding is that:
Therefore, if my understanding of the intent of the Accept function is correct, the following should hold true:
For example: It is possible that a browser could use the Accept header with the value text/plain;charset=UTF-8;q=0.8,text/plain;charset=US-ASCII;q=0.4,text/html. While text/html is typically assumed to have a default quality value of 1 if not specified, the browser could still explicitly specify it with a quality value of 1, as it is allowed by the HTTP specifications.
Furthermore, the browser could be indicating to the server that it prefers text/plain with a charset of UTF-8 over text/plain with a charset of US-ASCII, but it is still willing to accept both with different quality values. This could be useful in scenarios where the server has content in both character sets and wants to serve the content that is preferred by the client.
However, it is worth noting that the Accept header sent by the browser may vary depending on the user agent and its configuration. Therefore, it is important for the server to properly parse and interpret the Accept header to determine the preferred content type for the client.
Background/Refs
The relevant RFCs for the HTTP/1.1 specification are RFC 7231 and RFC 7232.
Regarding the q parameter and its default value of 1, Section 5.3.1 of RFC 7231 states:
Regarding the order of the MIME types in the Accept header, Section 5.3.2 of RFC 7231 states:
MDN on Quality Values https://developer.mozilla.org/en-US/docs/Glossary/Quality_values
Originally posted by @sixcolors in #2386 (comment)
The text was updated successfully, but these errors were encountered: