-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
feat(http-server): make http2 server creation extensible #2078
Conversation
|
||
/** | ||
* HTTP / HTTPS server used by LoopBack's RestServer | ||
* | ||
* @export | ||
* @class HttpServer | ||
*/ | ||
export class HttpServer { | ||
export class DefaultHttpServer implements HttpServer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the impact of this change on possible users of @loopback/http-server
?
For example, the following code creates a fully working HttpServer class in the current version (before your changes):
import * as turbo from 'turbo-http';
class TurboHttpServer extends HttpServer {
constructor(
requestListener: RequestListener,
serverOptions?: HttpServerOptions,
) {
super(requestListener, serverOptions);
this.server = turbo.createServer(requestListener);
}
}
IMO, we should preserve backwards compatibility in our API. Otherwise a new major version must be published, plus we will have to keep maintaining the old version according to our LTS policy...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can release @loopback/[email protected]
if we have to break the compatibility due to the nature of this work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can release @loopback/[email protected] if we have to break the compatibility due to the nature of this work.
Fair enough. In that case:
- The commit message in this PR should clearly describe the breaking changes so that our release tooling can pick it up. Ideally, there should be a short migration guide too.
- We need to update http-server's README with a table listing supported major versions (LTS terms). See https://github.com/strongloop/loopback#module-long-term-support-policy for inspiration. This should be done before 2.0.0 release, so that the new content is displayed on https://www.npmjs.com/package/@loopback/http-server
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also #2103
* @interface Http2Options | ||
*/ | ||
export interface Http2Options extends ListenerOptions { | ||
protocol: 'http2'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The built-in http2
module support HTTP/2 protocol over plain unencrypted socket (similar to HTTP) or over TLS socket (similar to HTTPS). How do you envision encoding this configuration flag when there is a single protocol value http2
?
await server.start(); | ||
expect(server.url).to.equal(`http://127.0.0.1:${server.port}`); | ||
}); | ||
|
||
it('supports HTTP/2 protocol with key and certificate files', async () => { | ||
// For some reason, spdy fails with Node 11 | ||
if (+process.versions.node.split('.')[0] > 10) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This early return will mark the test as passed, leading to false sense of security.
Please mark the test as skipped on Node.js 11.x. If we don't have a helper like "it.skipIf", then please consider creating one in @loopback/testlab
.
it.skipIf(
// For some reason, spdy fails with Node 11
+process.versions.node.split('.')[0] > 10),
'supports HTTP/2 protocol with key and certificate files',
async () => {
// ...
}
);
if (+process.versions.node.split('.')[0] > 10) return; | ||
const serverOptions: Http2Options = Object.assign( | ||
{ | ||
protocol: 'http2' as 'http2', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'http2' as 'http2'
This looks weird, are you sure it's needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't cast 'http2'
to 'http2'
, TS complains as it infers the type of http2
as string
, which is not compatible with 'http' | 'https' | 'http2'
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird.
Can you add a comment to explain to readers please? (Similarly for 'h2' below.)
{ | ||
protocol: 'http2' as 'http2', | ||
rejectUnauthorized: false, | ||
spdy: {protocols: ['h2' as 'h2']}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similarly here: 'h2' as 'h2'
?
// We need to close the agent so that server.close() returns | ||
// `@types/[email protected]` is not fully compatible with `[email protected]` | ||
// tslint:disable-next-line:no-any | ||
(agent as any).close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please close the agent before making assertion.
In your current version, when expect(response.statusCode)
throw an assertion error, then the agent is never closed.
packages/http-server/src/types.ts
Outdated
/** | ||
* A factory to create http servers | ||
*/ | ||
export interface HttpServerFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use a plain function instead of an interface please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to make it possible to use dependency injection for the implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Can you enhance the TSDoc comment to explain why we choose an interface instead of a function?
agent?: https.Agent, | ||
): Promise<IncomingMessage> { | ||
agent = | ||
agent || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the formatting of this code block rather weird. Have you considered the following alternative?
const defaultAgent = new https.Agent({
rejectUnauthorized: false,
});
export function httpsGetAsync(
urlString: string,
agent: https.Agent = defaultAgent,
): Promise<IncomingMessage> {
// agent is always set now
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1.
@raymondfeng I am not very happy with the high-level design. Let's discuss and see if we can improve it. I find unusual that we are injecting a factory. From what I've seen in applications using Dependency Injection, object creation (class instantiation) is usually handled by the IoC framework and the code receives the final object, not a factory to create it. Can we change the design so that the HTTP Server implementation (e.g app.bind(RestBindings.Http.SERVER).toClass(Http2Server) A typical HttpServer has two constructor-level dependencies: request-handler function and configuration object.
I think this change will greatly simplify both our implementation and also extensions providing custom servers. |
One more reason why I don't like HttpServerFactory is that HttpServer itself is implementing as a factory that accepts |
b9cb3e0
to
4d8c374
Compare
} | ||
|
||
export interface ProtocolServerFactory { | ||
supports(protocol: string, serverOptions: HttpServerOptions): boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. The good part about this design is that it follows what we have already in place for body parsers. However, I feel it's unnecessarily complicated for the case of http servers.
For long term, I think we need to provide http2
support based on Node.js core module http2
. From what I read, spdy
does not work on Node.js 10+. Once this is done, most users should not need to change the way how HTTP servers are created at all.
@raymondfeng @bajtos any news about this feature? |
This issue has been marked stale because it has not seen activity within six months. If you believe this to be in error, please contact one of the code owners, listed in the |
This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the |
Hey, we need http2 support in LB4, |
Supercedes #2065
Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated