Skip to content

Commit

Permalink
feat(h5p-server): added forward proxy support (#1414)
Browse files Browse the repository at this point in the history
  • Loading branch information
sr258 committed May 17, 2021
1 parent 17319b4 commit af18af1
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 19 deletions.
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* [Customization](advanced/customization.md)
* [Performance optimizations](advanced/performance-optimizations.md)
* [Privacy](advanced/privacy.md)
* [Forward proxy support](advanced/proxy.md)
- NPM packages
- h5p-mongos3
* [Mongo/S3 Content Storage](packages/h5p-mongos3/mongo-s3-content-storage.md)
Expand Down
26 changes: 26 additions & 0 deletions docs/advanced/proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Forward proxy support

@lumieducation/h5p-server has to make outgoing HTTPS requests to contact the H5P
Hub. If your network requires the use of a forward proxy to reach the Internet,
you must configure @lumieducation/h5p-server to use it for the HTTPS requests.

There are two ways of enabling the proxy:

1. Set the `proxy` property in IH5PConfig like this:
```json
{
"proxy": {
"host": "10.1.2.3",
"port": 8080,
"protocol": "https" // can also be left out or set to "http" if your proxy can't be accessed with https
}
}
```

2. Set the HTTPS_PROXY environment variable like this:

``HTTPS_PROXY=http://10.1.2.3:8080``
or
``HTTPS_PROXY=https://10.1.2.3:8080``

(depending on whether your proxy can be accessed with https).
41 changes: 41 additions & 0 deletions packages/h5p-server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/h5p-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"debug": "^4.1.1",
"fs-extra": "^9.0.0",
"get-all-files": "^3.0.0",
"https-proxy-agent": "^5.0.0",
"image-size": "^1.0.0",
"jsonpath": "^1.0.2",
"merge": "^2.0.0",
Expand Down
12 changes: 8 additions & 4 deletions packages/h5p-server/src/ContentHub.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import axios from 'axios';
import H5pError from './helpers/H5pError';
import { AxiosInstance } from 'axios';

import Logger from './helpers/Logger';
import HttpClient from './helpers/HttpClient';
import { IH5PConfig, IKeyValueStorage } from './types';
import H5pError from './helpers/H5pError';
import Logger from './helpers/Logger';

const log = new Logger('ContentHub');

Expand All @@ -14,8 +15,11 @@ export default class ContentHub {
*/
constructor(private config: IH5PConfig, private storage: IKeyValueStorage) {
log.info(`initialize`);
this.httpClient = HttpClient(config);
}

private httpClient: AxiosInstance;

getMetadata = async (lang?: string): Promise<any> => {
const updateKey = lang
? `contentHubMetadataUpdate-${lang}`
Expand Down Expand Up @@ -59,7 +63,7 @@ export default class ContentHub {
}
}

const response = await axios.get(
const response = await this.httpClient.get(
lang
? `${this.config.contentHubMetadataEndpoint}?lang=${isoLanguage}`
: this.config.contentHubMetadataEndpoint,
Expand Down
11 changes: 7 additions & 4 deletions packages/h5p-server/src/ContentTypeCache.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import axios from 'axios';
import { AxiosInstance } from 'axios';
import { crc32 } from 'crc';
import * as merge from 'merge';
import * as qs from 'qs';
Expand All @@ -12,6 +12,7 @@ import {
IRegistrationData,
IUsageStatistics
} from './types';
import HttpClient from './helpers/HttpClient';

const log = new Logger('ContentTypeCache');

Expand Down Expand Up @@ -39,10 +40,13 @@ export default class ContentTypeCache {
log.info(`initialize`);
this.config = config;
this.storage = storage;
this.httpClient = HttpClient(config);
}

private config: IH5PConfig;
private storage: IKeyValueStorage;
private httpClient: AxiosInstance;

/**
* Converts an entry from the H5P Hub into a format with flattened versions and integer date values.
* @param entry the entry as received from H5P Hub
Expand Down Expand Up @@ -102,8 +106,7 @@ export default class ContentTypeCache {
this.compileUsageStatistics()
);
}

const response = await axios.post(
const response = await this.httpClient.post(
this.config.hubContentTypesEndpoint,
qs.stringify(formData)
);
Expand Down Expand Up @@ -222,7 +225,7 @@ export default class ContentTypeCache {
if (this.config.uuid && this.config.uuid !== '') {
return this.config.uuid;
}
const response = await axios.post(
const response = await this.httpClient.post(
this.config.hubRegistrationEndpoint,
this.compileRegistrationData()
);
Expand Down
8 changes: 5 additions & 3 deletions packages/h5p-server/src/ContentTypeInformationRepository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import axios from 'axios';
import { AxiosInstance } from 'axios';
import * as fsExtra from 'fs-extra';
import promisepipe from 'promisepipe';
import { withFile } from 'tmp-promise';
Expand All @@ -17,9 +17,9 @@ import {
ITranslationFunction,
IUser
} from './types';

import Logger from './helpers/Logger';
import TranslatorWithFallback from './helpers/TranslatorWithFallback';
import HttpClient from './helpers/HttpClient';

const log = new Logger('ContentTypeInformationRepository');

Expand Down Expand Up @@ -55,9 +55,11 @@ export default class ContentTypeInformationRepository {
'hub'
]);
}
this.httpClient = HttpClient(config);
}

private translator: TranslatorWithFallback;
private httpClient: AxiosInstance;

/**
* Gets the information about available content types with all the extra
Expand Down Expand Up @@ -132,7 +134,7 @@ export default class ContentTypeInformationRepository {
}

// Download content type package from the Hub
const response = await axios.get(
const response = await this.httpClient.get(
this.config.hubContentTypesEndpoint + machineName,
{ responseType: 'stream' }
);
Expand Down
3 changes: 2 additions & 1 deletion packages/h5p-server/src/H5PEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,8 @@ export default class H5PEditor {
async ({ path: tmpFile }) => {
await downloadFile(
`${this.config.contentHubContentEndpoint}/${contentHubId}/export`,
tmpFile
tmpFile,
this.config
);
log.debug(`Hub content downloaded to ${tmpFile}`);
return this.uploadPackage(tmpFile, user);
Expand Down
33 changes: 33 additions & 0 deletions packages/h5p-server/src/helpers/HttpClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios, { AxiosInstance } from 'axios';
// We need to use https-proxy-agent as the default Axios proxy functionality
// doesn't work with https.
import HttpsProxyAgent from 'https-proxy-agent';
import { IH5PConfig } from '../types';

/**
* Creates an Axios instance that supports corporate HTTPS proxies.
* The proxy can either be configured in the config's proxy property or by
* setting the environment variable HTTPS_PROXY.
* @param config the H5P config object
* @returns the AxiosInstance
*/
const getClient = (config: IH5PConfig): AxiosInstance => {
let proxyAgent;

if (config.proxy) {
proxyAgent = HttpsProxyAgent({
host: config.proxy.host,
port: config.proxy.port,
protocol: config.proxy.protocol === 'https' ? 'https:' : undefined
});
} else if (process.env.HTTPS_PROXY) {
proxyAgent = HttpsProxyAgent(process.env.HTTPS_PROXY);
}

return axios.create({
proxy: proxyAgent ? false : undefined,
httpsAgent: proxyAgent
});
};

export default getClient;
12 changes: 8 additions & 4 deletions packages/h5p-server/src/helpers/downloadFile.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import axios, { AxiosError } from 'axios';
import { AxiosError } from 'axios';
import fs from 'fs';

import * as stream from 'stream';
import { promisify } from 'util';

import { IH5PConfig } from '../types';
import H5pError from './H5pError';
import HttpClient from './HttpClient';

const finished = promisify(stream.finished);

Expand All @@ -16,10 +18,12 @@ const finished = promisify(stream.finished);
*/
export async function downloadFile(
fileUrl: string,
outputLocationPath: string
outputLocationPath: string,
config: IH5PConfig
): Promise<any> {
const writer = fs.createWriteStream(outputLocationPath);
return axios({
const client = HttpClient(config);
return client({
method: 'get',
url: fileUrl,
responseType: 'stream'
Expand Down
9 changes: 8 additions & 1 deletion packages/h5p-server/src/implementation/H5PConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,17 @@ export default class H5PConfig implements IH5PConfig {
[machineName: string]: string[];
};
public playUrl: string = '/play';
public proxy?: {
host: string;
port: number;
protocol?: 'http' | 'https';
};
public sendUsageStatistics: boolean = false;
public setFinishedUrl: string = '/setFinished';
public siteType: 'local' | 'network' | 'internet' = 'local';
public temporaryFileLifetime: number = 120 * 60 * 1000; // 120 minutes
public temporaryFilesUrl: string = '/temp-files';
public uuid: string = ''; // TODO: revert to''
public uuid: string = '';

private storage: IKeyValueStorage;

Expand All @@ -121,6 +126,7 @@ export default class H5PConfig implements IH5PConfig {
await this.loadSettingFromStorage('maxFileSize');
await this.loadSettingFromStorage('maxTotalSize');
await this.loadSettingFromStorage('playerAddons');
await this.loadSettingFromStorage('proxy');
await this.loadSettingFromStorage('sendUsageStatistics');
await this.loadSettingFromStorage('siteType');
await this.loadSettingFromStorage('uuid');
Expand Down Expand Up @@ -148,6 +154,7 @@ export default class H5PConfig implements IH5PConfig {
await this.saveSettingToStorage('maxFileSize');
await this.saveSettingToStorage('maxTotalSize');
await this.saveSettingToStorage('playerAddons');
await this.saveSettingToStorage('proxy');
await this.saveSettingToStorage('sendUsageStatistics');
await this.saveSettingToStorage('siteType');
await this.saveSettingToStorage('uuid');
Expand Down
23 changes: 21 additions & 2 deletions packages/h5p-server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1511,8 +1511,27 @@ export interface IH5PConfig {
*/
playUrl: string;
/**
* If true, the instance will send usage statistics to the H5P Hub whenever it looks for new content types or updates.
* User-configurable.
* Allows settings a http(s) proxy for outgoing requests. No proxy will be
* used if left undefined (or the one specified in the HTTPS_PROXY
* environment variable).
*/
proxy?: {
/**
* The hostname of the proxy without any protocol prefix (e.g. only 10.1.2.3)
*/
host: string;
/**
* The port of the proxy, e.g. 8080.
*/
port: number;
/**
* The protocol to use to access the proxy. Can be left undefined if http is used.
*/
protocol?: 'http' | 'https';
};
/**
* If true, the instance will send usage statistics to the H5P Hub whenever
* it looks for new content types or updates. User-configurable.
*/
sendUsageStatistics: boolean;
/**
Expand Down

0 comments on commit af18af1

Please sign in to comment.