/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format * */ "use strict"; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const HttpError = require("./HttpError"); const NetworkError = require("./NetworkError"); const http = require("http"); const https = require("https"); const url = require("url"); const zlib = require("zlib"); const ZLIB_OPTIONS = { level: 9 }; const NULL_BYTE = 0x00; const NULL_BYTE_BUFFER = Buffer.from([NULL_BYTE]); class HttpStore { constructor(options) { const uri = url.parse(options.endpoint); const module = uri.protocol === "http:" ? http : https; const agentConfig = { family: options.family, keepAlive: true, keepAliveMsecs: options.timeout || 5000, maxSockets: 64, maxFreeSockets: 64 }; if (!uri.hostname || !uri.pathname) { throw new TypeError("Invalid endpoint: " + options.endpoint); } this._module = module; this._timeout = options.timeout || 5000; this._host = uri.hostname; this._path = uri.pathname; this._port = +uri.port; this._getAgent = new module.Agent(agentConfig); this._setAgent = new module.Agent(agentConfig); } get(key) { return new Promise((resolve, reject) => { const options = { agent: this._getAgent, host: this._host, method: "GET", path: this._path + "/" + key.toString("hex"), port: this._port, timeout: this._timeout }; const req = this._module.request(options, res => { const code = res.statusCode; const data = []; if (code === 404) { res.resume(); resolve(null); return; } else if (code !== 200) { res.resume(); reject(new HttpError("HTTP error: " + code, code)); return; } const gunzipped = res.pipe(zlib.createGunzip()); gunzipped.on("data", chunk => { data.push(chunk); }); gunzipped.on("error", err => { reject(err); }); gunzipped.on("end", () => { try { const buffer = Buffer.concat(data); if (buffer.length > 0 && buffer[0] === NULL_BYTE) { resolve(buffer.slice(1)); } else { resolve(JSON.parse(buffer.toString("utf8"))); } } catch (err) { reject(err); } }); res.on("error", err => gunzipped.emit("error", err)); }); req.on("error", err => { reject(new NetworkError(err.message, err.code)); }); req.end(); }); } set(key, value) { return new Promise((resolve, reject) => { const gzip = zlib.createGzip(ZLIB_OPTIONS); const options = { agent: this._setAgent, host: this._host, method: "PUT", path: this._path + "/" + key.toString("hex"), port: this._port, timeout: this._timeout }; const req = this._module.request(options, res => { res.on("error", err => { reject(err); }); res.on("end", () => { resolve(); }); // Consume all the data from the response without processing it. res.resume(); }); gzip.pipe(req); if (value instanceof Buffer) { gzip.write(NULL_BYTE_BUFFER); gzip.end(value); } else { gzip.end(JSON.stringify(value) || "null"); } }); } clear() { // Not implemented. } } _defineProperty(HttpStore, "HttpError", HttpError); _defineProperty(HttpStore, "NetworkError", NetworkError); module.exports = HttpStore;