-
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
Implement HTTP endpoint factory #1369
Conversation
try { | ||
await pEvent(this.httpServer, 'listening'); | ||
const address = this.httpServer.address() as AddressInfo; | ||
this.restServer.bind('rest.port').to(address.port); |
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.
@bajtos I wanted to use RestBindings.PORT
instead of rest.port
, but RestBindings
itself is undefined when {RestBindings}
is imported into http-sever.ts
- http-sever.ts
is executed before the RestBindings
object is created.
If there is a solution, do let me know.
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.
IMO, HttpServer
should not deal with dependency injection at all. Instead, it should provide properties like port
, hostname
and perhaps url
that can be used by consumers to update any bindings as necessary.
class HttpServer {
get port(): number {
return this.address().port;
}
address: AddressInfo {
return this.address() as AddressInfo;
}
}
Later in RestServer:
async start() {
// create http server and start it, then:
this.bind(RestBindings.PORT).to(server.port);
}
packages/http-server/package.json
Outdated
"build:current": "lb-tsc", | ||
"build:dist8": "lb-tsc es2017", | ||
"build:dist10": "lb-tsc es2018", | ||
"clean": "lb-clean loopback-core*.tgz dist* package api-docs", |
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.
Replace loopback-core*.tgz
with loopback-http-server*.tgz
. Same in verify
script below.
packages/http-server/package.json
Outdated
"README.md", | ||
"index.js", | ||
"index.d.ts", | ||
"dist/src", |
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 believe we need to include all dist*
folders. Here is the canonical form we are using in other packages:
"files": [
"README.md",
"index.js",
"index.d.ts",
"dist*/src",
"dist*/index*",
"src"
],
/** | ||
* Object for specifyig the HTTP / HTTPS server options | ||
*/ | ||
export type HttpOptions = { |
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.
HTTP can refer to both client and server. I am proposing HttpServerOptions
for more clarity.
* @class HttpServer | ||
*/ | ||
export class HttpServer { | ||
private restServer: RestServer; |
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.
My understanding is that HttpServer
should be a low-level building block used by higher-level classes like RestServer, maybe GrpcServer, etc. In which case HttpServer
must not depend on any specific high-level server, such dependency goes in a reverse direction inside the dependency graph.
private restServer: RestServer; | ||
private httpPort: number; | ||
private httpHost: string | undefined; | ||
private httpServer: 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.
Is it necessary to prefix all variables with http
, considering that they belong to a class that already has http
in the name?
it('stops server', async () => { | ||
const server = await givenAServer(); | ||
await server.start(); | ||
await server.stop(); |
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 - I think you need to verify that the server is no longer listening on the given port, e.g. a request fails.
import {Application} from '@loopback/core'; | ||
import * as assert from 'assert'; | ||
|
||
describe('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.
AFAICT, these tests are just duplicating integration tests - what's the point?
IMO, unit tests for an http server class makes little sense. You would have to mock core HTTP module, I think the complexity of that is bigger than the benefits of testing in isolation.
I am proposing to remove unit tests completely and move any relevant bits to integration tests.
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 am proposing to remove unit tests completely and move any relevant bits to integration tests.
Yes, totally agree. I didn't feel OK as I was writing those tests.
packages/rest/src/rest.server.ts
Outdated
@@ -167,6 +166,15 @@ export class RestServer extends Context implements Server, HttpServerLike { | |||
this._setupRequestHandler(options); | |||
|
|||
this.bind(RestBindings.HANDLER).toDynamicValue(() => this.httpHandler); | |||
|
|||
this._httpServer = new 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.
We need to defer creation of HttpServer instance until start
is called, so that we can pick any changes in HOST/PORT bindings.
An outline of a test case to write:
const server = new RestServer({port: 80});
server.bind(RestBindings.PORT).to(0);
await server.start();
// get the actual port number and verify it's not 80
packages/rest/src/rest.server.ts
Outdated
|
||
const httpPort = await this.get<number>(RestBindings.PORT); | ||
const httpHost = await this.get<string | undefined>(RestBindings.HOST); | ||
this._httpServer = createServer(this.requestHandler); |
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.
Continuing my previous comment - this is the place where to instantiate HttpServer
as a replacement for http.createServer
. We need to preserve the logic obtaining the latest values for port & host.
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.
Cross-posting #1369 (comment)
An outline of a test case to write:
const server = new RestServer({port: 80});
server.bind(RestBindings.PORT).to(0);
await server.start();
// get the actual port number and verify it's not 80
packages/http-server/README.md
Outdated
|
||
const app = new Application(); | ||
const restServer = new RestServer(app); | ||
const httpServer = new HttpServer(restServer, {port: 3000, host: ''}, (req, res) => {}); |
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 example will need to be updated once we remove the dependency from HttpServer to RestServer, see my comments below.
I think it also makes more sense to reorder constructor arguments to accept request handler as the first arg and options as the second.
I would like something along the following lines:
import {HttpServer} from '@loopback/http-server';
const httpServer = new HttpServer(
(req, res) => res.end('Hello world'),
{port: 3000}
);
packages/http-server/package.json
Outdated
"license": "MIT", | ||
"dependencies": { | ||
"@loopback/dist-util": "^0.3.1", | ||
"@loopback/rest": "^0.10.4", |
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 remove this (circular) dependency.
packages/http-server/package.json
Outdated
"@loopback/build": "^0.6.5", | ||
"@loopback/core": "^0.8.4", | ||
"@loopback/testlab": "^0.10.4", | ||
"@types/debug": "^0.0.30", |
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 looks like an unused dependency to me, please remove.
packages/http-server/package.json
Outdated
@@ -0,0 +1,50 @@ | |||
{ | |||
"name": "@loopback/http-server", | |||
"version": "0.0.0", |
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 think the first version would be "0.0.1"?
I don't know why we completely threw away the abstraction at https://github.com/strongloop/loopback-next/blob/2bddc47ccda56a50c52a8a92facd87e3a3f4944d/packages/http-server/src/common.ts. IMO, the PoC only requires some clean up to align with latest code in master. I agree with @bajtos's comments that |
@raymondfeng, @bajtos suggested to keep this PR as simple as possible and build from there. |
I thought the idea was to decompose my original PR into a few steps and refine each of them as we go. If we can combine
|
Yes, that's my idea too! My general opinion is that we should treat spikes/PoCs as throw-away code and build the production implementation from scratch using test-driven/test-first development. Otherwise it's too easy to overlook that some parts of spike code that leaked into production are not in production quality and/or don't have enough test coverage. In this particular case, the PoC was created with the assumption that we are going to support multiple server frameworks (Koa, Express), which introduced additional complexity to the API. For example, consider So far, we were following the plan outlined in #1082 (comment):
Since there was no follow-up comment from you, we assumed you are ok with my proposal 🤷♂️
This is a good list that allows me to better understand your vision. I don't remember seeing such list before, it would be great to have it at a prominent place in your PoC PR or some other place where its easy to find. Having said that, the fault may be at my side too, these changes are in progress for more than a month. Back to the topic: The first three items make perfect sense and I think that's exactly the scope of this pull request. I have concerns about putting marshaling/unmarshaling and middleware capabilities into our shared low-level HTTP stack. I'd like us to work on these two changes in a different pull request(s), so that the initial set of features can be landed faster, and then we can keep discussions about the remaining two items focused and in a single place. Thoughts? |
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.
You are making good progress 👍
this._host = '127.0.0.1'; | ||
} | ||
} | ||
this._url = `${this._protocol}://${this.host}:${this.port}`; |
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 believe using private properties with public getters is sort of an anti-pattern in JavaScript/TypeScript.
I am proposing to either move the logic building this._url
to url
getter function, or remove url
getter function and rename private _url
to public url
.
} | ||
if (process.env.TRAVIS) { | ||
// Travis CI seems to have trouble connecting to '[::]' or '[::1]' | ||
// Set host to `127.0.0.1` |
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
We cannot ship workarounds for Travis CI in code that will be used by LB apps in production!
If IPv6 localhost addresses are causing problems in Travis, then let's fix our tests to explicitly listen on 127.0.0.1
instead.
this._port = this._address.port; | ||
this._protocol = this._protocol || 'http'; | ||
if (this._address.family === 'IPv6') { | ||
this._host = `[${this._host}]`; |
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.
In a host name in the form [${IPv6 addr}]
a valid IPv6 address? I mean what if somebody calls tcp.connect(httpServer.port, httpServer.host)
- shouldn't host
remain the same value as provided in the config?
In general, I find it is suspicious that changes in the format needed for URL are stored in server properties that have more wider usage than just URL.
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.
Ok, let's address the format when and where encountered. Will remove the formatting code.
res.end(); | ||
}, | ||
{ | ||
port: 9000, |
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.
Using hard-coded port numbers in tests is a bad practice, because the selected port can be already used on that machine, for example by another run of the same test suite. It also makes it difficult to run tests in parallel.
Please always use port 0
and let the operating system pick an available port for you.
await server.stop(); | ||
}); | ||
|
||
// @bajtos how do we test this? |
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.
For example, you can verify that request
fails with an error having code
set to string ECONNREFUSED
.
expect(() => request(server.url))
.to.be.rejectedWith({code: 'ECONNREFUSED'});
const server = await app.getServer(RestServer); | ||
server.bind(RestBindings.PORT).to(0); | ||
await server.start(); | ||
// @bajtos `_httpServer` of `server` is protected, how do we check the port and other details here? |
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 see the earlier comments about the fact that HttpServer
should not be updating any bindings.
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.
@bajtos that's a RestServer
instance. HttpServer
does not do any sort of binding now, it just starts a server and exposes properties about that 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.
I see. Since HttpServer does not deal with any bindings, this test does not belong to packages/rest
. packages/rest
is the place where to test this functionality - I would be surprised if there wasn't any existing test for that.
packages/rest/src/rest.server.ts
Outdated
} | ||
}); | ||
}); | ||
return this._httpServer.stop(); |
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.
Now that you are directly returning the promise provided by stop
, this function no longer needs to be async.
- async stop() {
+ stop() {
// Kill the server instance.
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.
Will await
this._httpServer.stop()
, instead.
/** | ||
* Protocol of the HTTP / HTTPS server | ||
*/ | ||
public get protocol(): 'http' | 'https' { |
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 define a shared type for 'http' | 'https'
and use it all places where you are duplicating this union now.
E.g.
export type HttpProtocol = 'http' | 'https';
this._address = this.httpServer.address() as AddressInfo; | ||
this._host = this._host || this._address.address; | ||
this._port = this._address.port; | ||
this._protocol = this._protocol || 'http'; |
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 move this initialization to the constructor.
f95ab29
to
b455b33
Compare
*/ | ||
export type HttpServerOptions = { | ||
port: number; | ||
host?: string | undefined; |
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.
host?
already allows undefined
, no need to repeat that again in the type definition.
host?: string;
packages/rest/src/rest.server.ts
Outdated
port: options.port, | ||
host: options.host, | ||
protocol: options.protocol || 'http', | ||
}); |
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 move the initialization of _httpServer
to _setupRequestHandler
method below.
const server = new HttpServer( | ||
(req, res) => { | ||
res.end(); | ||
}, |
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 extract this request handler that's repeated in all tests into a shared helper function.
const server = new HttpServer(
dummyRequestHandler,
// ...
);
(req, res) => { | ||
res.end(); | ||
}, | ||
{port: 0}, |
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.
Would it make sense to make the port
setting optional and defaulting to 0
?
Intended usage:
const server = new HttpServer(requestHandler);
It would also allow us to simplify RestServerConfig
- instead of copying all interface members, we could leverage inheritance.
interface RestServerConfig extends HttpServerConfig {
// ...
}
Thoughts?
Since we have settled on Express, the generic parameters are not needed any more. Such type of cleanup is what I had in mind for this task.
|
b455b33
to
e1a8f71
Compare
// License text available at https://opensource.org/licenses/MIT | ||
import {HttpServer} from '../../'; | ||
import {Application, ApplicationConfig} from '@loopback/core'; | ||
import {RestServer, RestBindings, RestComponent} from '@loopback/rest'; |
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 remove any references to @loopback/rest
to avoid a cyclic dependency.
const server = await givenARestServer({rest: {port: 80}}); | ||
await server.bind(RestBindings.PORT).to(0); | ||
await server.start(); | ||
expect(server.getSync(RestBindings.PORT)).to.not.equal(80); |
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 move this test to packages/rest
.
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.
Indeed, it belongs to packages/rest
.
13d4f8e
to
c24e20b
Compare
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.
Two more comments, the rest of the patch LGTM.
Please wait for @raymondfeng's approval before landing.
this._port = (httpServerOptions && httpServerOptions.port) || 0; | ||
this._host = (httpServerOptions && httpServerOptions.host) || undefined; | ||
this._protocol = | ||
(httpServerOptions && httpServerOptions.protocol) || 'http'; |
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.
DRY.
if (!httpServerOptions) httpServerOptions = {};
this._port = httpServerOptions.port || 0;
// ...
I think we can also shorten the argument name to simply options
.
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.
Indeed!
/** | ||
* Starts the HTTP / HTTPS server | ||
*/ | ||
public async start(port?: number, host?: string) { |
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.
Let's keep start
parameterless please - both host and port should be configured at the time when the server is created. Unless there is a good justification why we need these two parameters?
5d44eaa
to
1890eb3
Compare
@raymondfeng All checks passed. Any comments? |
1890eb3
to
0c0f1fd
Compare
please investigate https://travis-ci.org/strongloop/loopback-next/jobs/388859072 |
* Stops the HTTP / HTTPS server | ||
*/ | ||
public async stop() { | ||
this.httpServer.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.
We should check if this.httpServer
is set.
/** | ||
* Protocol of the HTTP / HTTPS server | ||
*/ | ||
public get protocol(): HttpProtocol { |
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.
Why bother to have getter
for these properties? Do you want to enforce readonly
?
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.
Yes, readonly
. What do you suggest?
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.
Have you tried readonly
keyword from TypeScript?
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.
Thanks, will use that.
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 think we can use readonly
modifier for all properties excluding port
, because port
changes as a result of start()
call. OTOH, maybe we should keep a readonly _port
property to hold the requested port (e.g. 0
) and then write a port
getter that will either return the actual port reported by this._httpServer.address().port
or fall back to this._port
.
This has an important benefit that multiple start/stop cycles of a server configured to listen on port 0 will always pick a new port. At the moment, I believe subsequent start()
calls will pick the temporary port number assigned by the operating system for the first start()
call.
@hacksparrow Please add a unit-test to verify the behavior of our server in this edge case.
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.
@raymondfeng even an undefined host
will behave like port
set to 0
. If the host
value is not set during instantiation, the HttpServer
instance will store it as undefined
, however, the underlying http
/https
will generate a host value for the server - something like ::
.
Since url
is dependent on host
and port
, the only property that can be implemented as a readonly
would be protocol
.
@bajtos I will add tests for both host
and port
.
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.
Added.
Please also fix https://travis-ci.org/strongloop/loopback-next/jobs/388859076 |
0c0f1fd
to
5cffb71
Compare
5cffb71
to
c322380
Compare
*/ | ||
public async stop() { | ||
if (this.httpServer) { | ||
this.httpServer.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.
Should we expose the state of the HttpServer
, such as started: 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.
Good suggestion, will add. Initially I had listening
, but removed it.
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.
Added.
public async start() { | ||
this.httpServer = createServer(this.httpRequestListener); | ||
this.httpServer.listen(this._port, this._host); | ||
await pEvent(this.httpServer, 'listening'); |
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 wonder why we don't use a callback for httpServer.listen()
and close()
. For example:
import {promisify} from 'util';
public async start() {
this.httpServer = createServer(this.httpRequestListener);
await promisify(this.httpServer.listen)(this._port, this._host);
this._address = this.httpServer.address() as AddressInfo;
this._host = this._host || this._address.address;
this._port = this._address.port;
}
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.
Good idea, will add.
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 note that pEvent
will listen for error
events too. As a result, server.start()
returns a rejected promise if the server cannot be started.
The callback for httpServer.listen
is not error-first, it's just a regular event callback for listening
event. See https://nodejs.org/dist/latest-v10.x/docs/api/net.html#net_server_listen:
When the server starts listening, the 'listening' event will be emitted. The last parameter callback will be added as a listener for the 'listening' event.
@hacksparrow Please start by adding a test to verify that start()
returns a rejected promise when the server cannot be started. Quoting from the same doc page:
One of the most common errors raised when listening is
EADDRINUSE
. This happens when another server is already listening on the requested port / path / handle.
In the test:
- create and start a server listening on port
0
(let the OS pick an available port) - create another server, set the port number explicitly to the same value as reported by the server started in the previous step
- verify that
start()
returns a rejected promise and the error hascode
set toEADDRINUSE
- see http://shouldjs.github.io/#assertion-rejectedwith
Once we have that test in place, you can try to use the promisifed listen
method and see how easy/difficult it is to correctly handle errors too (compared to what we have 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.
@bajtos added the test.
@raymondfeng why have callbacks, when we can just await
?
* URL of the HTTP / HTTPS server | ||
*/ | ||
public get url(): string { | ||
return `${this._protocol}://${this.host}:${this.port}`; |
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.
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 to handle IPv6 localhost URLs correctly, although I am fine with leaving that out of scope of this pull request. Isn't there any Node.js core API or perhaps an npm module that can solve this problem for us?
The built-in url
API seems to work well
$ node
> require('url').format({protocol: 'http', hostname: '::1', port: 80, pathname: '/foo'})
'http://[::1]:80/foo'
The docs says this code is considered as legacy and should not be used in new code :(
Unfortunately the newer WhatWG API does not encode IPv6 local addressed as the old one, so I am not sure how much benefit we can get from using it.
See nodejs/help#1176 (comment)
const u = new (require('url').URL)('http://./');
u.protocol = 'https';
u.host = '[::1]';
u.port = 3000;
u.pathname = '/foo';
u.href
// returns
// 'https://[::1]:3000/foo'
-1 for Travis-specific hacks, see #1369 (comment)
We cannot ship workarounds for Travis CI in code that will be used by LB apps in production!
If IPv6 localhost addresses are causing problems in Travis, then let's fix our tests to explicitly listen on 127.0.0.1 instead.
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.
@raymondfeng can we do the "robustification" after landing the basic functionality?
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 force tests to only listen on 127.0.0.1
, we then lose the coverage for host === undefined
.
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. I am proposing to write two tests for host === undefined
case:
- One test to verify what is returned by
host
andurl
properties. I think we don't need to start the server for this and thus these tests can be at unit-test level. - One test to verify that server listening on all hosts accepts connections from
127.0.1
. The trick is to build the request URL manually (http://127.0.0.1:${server.port}/
) in the test, don't useserver.url
property.
c322380
to
1f89ee5
Compare
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.
LGTM as the first increment. I see few more opportunities to clean up the code, but that can be easily done later.
@slnode test please |
@slnode ok to test |
* Decouple the creation of HTTP/HTTPS server from the `rest` package. * Create new package (`http-server`) to handle the creation of HTTP/HTTPS server.
1f89ee5
to
bac8d8c
Compare
Implements #1331.
Main changes:
@loopback/http-server
@loopback/rest
to use@loopback/http-server
.Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated