Skip to content

Commit

Permalink
fix: SL-2377 host/forwarded headers support (#249)
Browse files Browse the repository at this point in the history
* fix: SL-2377 host/forwarded headers support

* fix: SL-2377 removed ts-ignore

* fix: SL-2377 tests

* fix: SL-2377 CR fixes
  • Loading branch information
karol-maciaszek committed Apr 16, 2019
1 parent ff6f46e commit f8a1131
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 19 deletions.
34 changes: 28 additions & 6 deletions packages/http/src/forwarder/HttpForwarder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { IForwarder, IPrismInput } from '@stoplight/prism-core';
import { IHttpOperation, IServer } from '@stoplight/types';
import axios from 'axios';
import { IHttpConfig, IHttpRequest, IHttpResponse } from '../types';
import { URL } from 'url';
import { IHttpConfig, IHttpNameValue, IHttpRequest, IHttpResponse } from '../types';

export class HttpForwarder
implements IForwarder<IHttpOperation, IHttpRequest, IHttpConfig, IHttpResponse> {
Expand All @@ -10,18 +11,23 @@ export class HttpForwarder
input: IPrismInput<IHttpRequest>;
}): Promise<IHttpResponse> {
const inputData = opts.input.data;
const baseUrl =
opts.resource && opts.resource.servers && opts.resource.servers.length > 0
? this.resolveServerUrl(opts.resource.servers[0])
: inputData.url.baseUrl;

if (!baseUrl) {
throw new Error('Either one server in spec or baseUrl in request must be defined');
}

const response = await axios({
method: inputData.method,
baseURL:
opts.resource && opts.resource.servers && opts.resource.servers.length > 0
? this.resolveServerUrl(opts.resource.servers[0])
: inputData.url.baseUrl,
baseURL: baseUrl,
url: inputData.url.path,
params: inputData.url.query,
responseType: 'text',
data: inputData.body,
headers: inputData.headers,
headers: this.updateHostHeaders(baseUrl, inputData.headers),
validateStatus: () => true,
});

Expand All @@ -32,6 +38,22 @@ export class HttpForwarder
};
}

private updateHostHeaders(baseUrl: string, headers?: IHttpNameValue) {
// no headers? do nothing
if (!headers) return headers;

// host header provided? override with actual hostname
if (headers.hasOwnProperty('host')) {
return {
...headers,
host: new URL(baseUrl).host,
forwarded: `host=${headers.host}`,
};
}

return headers;
}

private resolveServerUrl(server: IServer) {
if (!server.variables) {
return server.url;
Expand Down
89 changes: 76 additions & 13 deletions packages/http/src/forwarder/__tests__/HttpForwarder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import * as axios from 'axios';

import { httpOperations, httpRequests } from '../../__tests__/fixtures';
import { httpInputs, httpOperations, httpRequests } from '../../__tests__/fixtures';
import { HttpForwarder } from '../HttpForwarder';

jest.mock('axios', () => ({
default: jest.fn().mockResolvedValue({ status: 200 }),
}));

describe('HttpForwarder', () => {
const forwarder = new HttpForwarder();

describe('forward()', () => {
describe("parameters haven' been provided", () => {
describe("parameters haven't been provided", () => {
it('proxies request correctly', async () => {
jest.spyOn(axios, 'default').mockResolvedValue({
status: 200,
headers: {
'Content-type': 'application/json',
const request = {
...httpRequests[0],
data: {
...httpInputs[0],
url: { ...httpInputs[0].url, baseUrl: 'http://api.example.com' },
},
data: '[{},{}]',
statusText: 'ok',
config: {},
});

const request = Object.assign({}, httpRequests[0]);
request.data.url.baseUrl = 'http://api.example.com';
};

await forwarder.forward({ input: request });

Expand All @@ -34,6 +33,70 @@ describe('HttpForwarder', () => {
});
});

describe('no servers defined', () => {
describe('baseUrl is not set', () => {
it('throws error', async () => {
const request = Object.assign({}, httpRequests[0]);
await expect(forwarder.forward({ input: request })).rejects.toThrowError(
'Either one server in spec or baseUrl in request must be defined'
);
});
});
});

describe('headers are provided', () => {
describe('host header is not present', () => {
it('does not modifies headers', async () => {
const request = {
...httpRequests[0],
data: {
...httpInputs[0],
headers: { 'x-test': 'b' },
url: { ...httpInputs[0].url, baseUrl: 'http://api.example.com' },
},
};

await forwarder.forward({ input: request });

expect(axios.default).toHaveBeenCalledWith({
method: 'get',
url: '/todos',
baseURL: 'http://api.example.com',
responseType: 'text',
validateStatus: expect.any(Function),
headers: { 'x-test': 'b' },
});
});
});

describe('host header is present', () => {
it('modifies headers', async () => {
const request = {
...httpRequests[0],
data: {
...httpInputs[0],
headers: { host: 'localhost' },
url: { ...httpInputs[0].url, baseUrl: 'http://api.example.com' },
},
};

await forwarder.forward({ input: request });

expect(axios.default).toHaveBeenCalledWith({
method: 'get',
url: '/todos',
baseURL: 'http://api.example.com',
responseType: 'text',
validateStatus: expect.any(Function),
headers: {
host: 'api.example.com',
forwarded: 'host=localhost',
},
});
});
});
});

describe('parameters are valid', () => {
describe('server url has no variables', () => {
it('proxies request correctly', async () => {
Expand Down

0 comments on commit f8a1131

Please sign in to comment.