feat: introduce support for custom HTTP headers (#16599)

* feat: introduce support for custom http headers

* feat: add fetch middleware
This commit is contained in:
Josh 2021-04-26 08:35:07 -07:00 committed by GitHub
parent 6d160768d7
commit c44812fa71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 130 additions and 10 deletions

View File

@ -622,21 +622,45 @@ export type PerfSample = {
samplePeriodSecs: number; samplePeriodSecs: number;
}; };
function createRpcClient(url: string, useHttps: boolean): RpcClient { function createRpcClient(
url: string,
useHttps: boolean,
httpHeaders?: HttpHeaders,
fetchMiddleware?: FetchMiddleware,
): RpcClient {
let agentManager: AgentManager | undefined; let agentManager: AgentManager | undefined;
if (!process.env.BROWSER) { if (!process.env.BROWSER) {
agentManager = new AgentManager(useHttps); agentManager = new AgentManager(useHttps);
} }
let fetchWithMiddleware: (url: string, options: any) => Promise<Response>;
if (fetchMiddleware) {
fetchWithMiddleware = (url: string, options: any) => {
return new Promise<Response>((resolve, reject) => {
fetchMiddleware(url, options, async (url: string, options: any) => {
try {
resolve(await fetch(url, options));
} catch (error) {
reject(error);
}
});
});
};
}
const clientBrowser = new RpcClient(async (request, callback) => { const clientBrowser = new RpcClient(async (request, callback) => {
const agent = agentManager ? agentManager.requestStart() : undefined; const agent = agentManager ? agentManager.requestStart() : undefined;
const options = { const options = {
method: 'POST', method: 'POST',
body: request, body: request,
agent, agent,
headers: { headers: Object.assign(
'Content-Type': 'application/json', {
}, 'Content-Type': 'application/json',
},
httpHeaders || {},
),
}; };
try { try {
@ -644,7 +668,12 @@ function createRpcClient(url: string, useHttps: boolean): RpcClient {
let res: Response; let res: Response;
let waitTime = 500; let waitTime = 500;
for (;;) { for (;;) {
res = await fetch(url, options); if (fetchWithMiddleware) {
res = await fetchWithMiddleware(url, options);
} else {
res = await fetch(url, options);
}
if (res.status !== 429 /* Too many requests */) { if (res.status !== 429 /* Too many requests */) {
break; break;
} }
@ -1673,6 +1702,32 @@ export type ConfirmedSignatureInfo = {
blockTime?: number | null; blockTime?: number | null;
}; };
/**
* An object defining headers to be passed to the RPC server
*/
export type HttpHeaders = {[header: string]: string};
/**
* A callback used to augment the outgoing HTTP request
*/
export type FetchMiddleware = (
url: string,
options: any,
fetch: Function,
) => void;
/**
* Configuration for instantiating a Connection
*/
export type ConnectionConfig = {
/** Optional commitment level */
commitment?: Commitment;
/** Optional HTTP headers object */
httpHeaders?: HttpHeaders;
/** Optional fetch middleware callback */
fetchMiddleware?: FetchMiddleware;
};
/** /**
* A connection to a fullnode JSON RPC endpoint * A connection to a fullnode JSON RPC endpoint
*/ */
@ -1734,18 +1789,36 @@ export class Connection {
* Establish a JSON RPC connection * Establish a JSON RPC connection
* *
* @param endpoint URL to the fullnode JSON RPC endpoint * @param endpoint URL to the fullnode JSON RPC endpoint
* @param commitment optional default commitment level * @param commitmentOrConfig optional default commitment level or optional ConnectionConfig configuration object
*/ */
constructor(endpoint: string, commitment?: Commitment) { constructor(
endpoint: string,
commitmentOrConfig?: Commitment | ConnectionConfig,
) {
this._rpcEndpoint = endpoint; this._rpcEndpoint = endpoint;
let url = urlParse(endpoint); let url = urlParse(endpoint);
const useHttps = url.protocol === 'https:'; const useHttps = url.protocol === 'https:';
this._rpcClient = createRpcClient(url.href, useHttps); let httpHeaders;
let fetchMiddleware;
if (commitmentOrConfig && typeof commitmentOrConfig === 'string') {
this._commitment = commitmentOrConfig;
} else if (commitmentOrConfig) {
this._commitment = commitmentOrConfig.commitment;
httpHeaders = commitmentOrConfig.httpHeaders;
fetchMiddleware = commitmentOrConfig.fetchMiddleware;
}
this._rpcClient = createRpcClient(
url.href,
useHttps,
httpHeaders,
fetchMiddleware,
);
this._rpcRequest = createRpcRequest(this._rpcClient); this._rpcRequest = createRpcRequest(this._rpcClient);
this._rpcBatchRequest = createRpcBatchRequest(this._rpcClient); this._rpcBatchRequest = createRpcBatchRequest(this._rpcClient);
this._commitment = commitment;
this._blockhashInfo = { this._blockhashInfo = {
recentBlockhash: null, recentBlockhash: null,
lastFetch: 0, lastFetch: 0,
@ -1764,6 +1837,7 @@ export class Connection {
if (url.port !== null) { if (url.port !== null) {
url.port = String(Number(url.port) + 1); url.port = String(Number(url.port) + 1);
} }
this._rpcWebSocket = new RpcWebSocketClient(urlFormat(url), { this._rpcWebSocket = new RpcWebSocketClient(urlFormat(url), {
autoconnect: false, autoconnect: false,
max_reconnects: Infinity, max_reconnects: Infinity,

View File

@ -88,6 +88,49 @@ describe('Connection', () => {
}); });
} }
if (mockServer) {
it('should pass HTTP headers to RPC', async () => {
const headers = {
Authorization: 'Bearer 123',
};
let connection = new Connection(url, {
httpHeaders: headers,
});
await mockRpcResponse({
method: 'getVersion',
params: [],
value: {'solana-core': '0.20.4'},
withHeaders: headers,
});
expect(await connection.getVersion()).to.be.not.null;
});
it('should allow middleware to augment request', async () => {
let connection = new Connection(url, {
fetchMiddleware: (url, options, fetch) => {
options.headers = Object.assign(options.headers, {
Authorization: 'Bearer 123',
});
fetch(url, options);
},
});
await mockRpcResponse({
method: 'getVersion',
params: [],
value: {'solana-core': '0.20.4'},
withHeaders: {
Authorization: 'Bearer 123',
},
});
expect(await connection.getVersion()).to.be.not.null;
});
}
it('get account info - not found', async () => { it('get account info - not found', async () => {
const account = new Account(); const account = new Account();

View File

@ -5,7 +5,7 @@ import * as mockttp from 'mockttp';
import {mockRpcMessage} from './rpc-websockets'; import {mockRpcMessage} from './rpc-websockets';
import {Account, Connection, PublicKey, Transaction} from '../../src'; import {Account, Connection, PublicKey, Transaction} from '../../src';
import type {Commitment, RpcParams} from '../../src/connection'; import type {Commitment, HttpHeaders, RpcParams} from '../../src/connection';
export const mockServer: mockttp.Mockttp | undefined = export const mockServer: mockttp.Mockttp | undefined =
process.env.TEST_LIVE === undefined ? mockttp.getLocal() : undefined; process.env.TEST_LIVE === undefined ? mockttp.getLocal() : undefined;
@ -64,12 +64,14 @@ export const mockRpcResponse = async ({
value, value,
error, error,
withContext, withContext,
withHeaders,
}: { }: {
method: string; method: string;
params: Array<any>; params: Array<any>;
value?: any; value?: any;
error?: any; error?: any;
withContext?: boolean; withContext?: boolean;
withHeaders?: HttpHeaders;
}) => { }) => {
if (!mockServer) return; if (!mockServer) return;
@ -90,6 +92,7 @@ export const mockRpcResponse = async ({
method, method,
params, params,
}) })
.withHeaders(withHeaders || {})
.thenReply( .thenReply(
200, 200,
JSON.stringify({ JSON.stringify({