azurite
Version:
An open source Azure Storage API compatible server
343 lines • 19.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BlobBatchHandler = void 0;
const tslib_1 = require("tslib");
const ms_rest_js_1 = require("@azure/ms-rest-js");
const AccountSASAuthenticator_1 = tslib_1.__importDefault(require("../authentication/AccountSASAuthenticator"));
const BlobSASAuthenticator_1 = tslib_1.__importDefault(require("../authentication/BlobSASAuthenticator"));
const BlobSharedKeyAuthenticator_1 = tslib_1.__importDefault(require("../authentication/BlobSharedKeyAuthenticator"));
const BlobTokenAuthenticator_1 = tslib_1.__importDefault(require("../authentication/BlobTokenAuthenticator"));
const PublicAccessAuthenticator_1 = tslib_1.__importDefault(require("../authentication/PublicAccessAuthenticator"));
const BlobStorageContext_1 = tslib_1.__importDefault(require("../context/BlobStorageContext"));
const StorageError_1 = tslib_1.__importDefault(require("../errors/StorageError"));
const StorageErrorFactory_1 = tslib_1.__importDefault(require("../errors/StorageErrorFactory"));
const operation_1 = tslib_1.__importDefault(require("../generated/artifacts/operation"));
const Context_1 = tslib_1.__importDefault(require("../generated/Context"));
const MiddlewareError_1 = tslib_1.__importDefault(require("../generated/errors/MiddlewareError"));
const deserializer_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/deserializer.middleware"));
const dispatch_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/dispatch.middleware"));
const end_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/end.middleware"));
const error_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/error.middleware"));
const HandlerMiddlewareFactory_1 = tslib_1.__importDefault(require("../generated/middleware/HandlerMiddlewareFactory"));
const serializer_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/serializer.middleware"));
const AuthenticationMiddlewareFactory_1 = tslib_1.__importDefault(require("../middlewares/AuthenticationMiddlewareFactory"));
const blobStorageContext_middleware_1 = require("../middlewares/blobStorageContext.middleware");
const constants_1 = require("../utils/constants");
const AppendBlobHandler_1 = tslib_1.__importDefault(require("./AppendBlobHandler"));
const BlobBatchSubRequest_1 = require("./BlobBatchSubRequest");
const BlobBatchSubResponse_1 = require("./BlobBatchSubResponse");
const BlobHandler_1 = tslib_1.__importDefault(require("./BlobHandler"));
const BlockBlobHandler_1 = tslib_1.__importDefault(require("./BlockBlobHandler"));
const ContainerHandler_1 = tslib_1.__importDefault(require("./ContainerHandler"));
const PageBlobHandler_1 = tslib_1.__importDefault(require("./PageBlobHandler"));
const PageBlobRangesManager_1 = tslib_1.__importDefault(require("./PageBlobRangesManager"));
const ServiceHandler_1 = tslib_1.__importDefault(require("./ServiceHandler"));
class BlobBatchHandler {
constructor(accountDataStore, oauth, metadataStore, extentStore, logger, loose, disableProductStyle) {
this.accountDataStore = accountDataStore;
this.oauth = oauth;
this.metadataStore = metadataStore;
this.extentStore = extentStore;
this.logger = logger;
this.loose = loose;
this.disableProductStyle = disableProductStyle;
const subRequestContextMiddleware = (req, res, locals, next) => {
const urlbuilder = ms_rest_js_1.URLBuilder.parse(req.getUrl());
(0, blobStorageContext_middleware_1.internalBlobStorageContextMiddleware)(new BlobStorageContext_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH), req, res, urlbuilder.getHost(), urlbuilder.getPath(), next, true, this.disableProductStyle, this.loose);
};
const subRequestDispatchMiddleware = (req, res, locals, next) => {
(0, dispatch_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), req, next, this.logger);
};
// AuthN middleware, like shared key auth or SAS auth
const authenticationMiddlewareFactory = new AuthenticationMiddlewareFactory_1.default(this.logger);
this.authenticators = [
new PublicAccessAuthenticator_1.default(this.metadataStore, logger),
new BlobSharedKeyAuthenticator_1.default(this.accountDataStore, logger),
new AccountSASAuthenticator_1.default(this.accountDataStore, this.metadataStore, logger),
new BlobSASAuthenticator_1.default(this.accountDataStore, this.metadataStore, logger)
];
if (this.oauth !== undefined) {
this.authenticators.push(new BlobTokenAuthenticator_1.default(this.accountDataStore, this.oauth, logger));
}
const subRequestAuthenticationMiddleware = (req, res, locals, next) => {
const context = new BlobStorageContext_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH);
authenticationMiddlewareFactory.authenticate(context, req, res, this.authenticators).then(pass => {
// TODO: To support public access, we need to modify here to reject request later in handler
if (pass) {
next();
}
else {
next(StorageErrorFactory_1.default.getAuthorizationFailure(context.contextId));
}
})
.catch(errorInfo => next(errorInfo));
};
const subRequestDeserializeMiddleware = (req, res, locals, next) => {
(0, deserializer_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), req, next, this.logger);
};
this.handlers = {
appendBlobHandler: new AppendBlobHandler_1.default(this.metadataStore, this.extentStore, this.logger, this.loose),
blobHandler: new BlobHandler_1.default(this.metadataStore, this.extentStore, this.logger, this.loose, new PageBlobRangesManager_1.default()),
blockBlobHandler: new BlockBlobHandler_1.default(this.metadataStore, this.extentStore, this.logger, this.loose),
containerHandler: new ContainerHandler_1.default(this.accountDataStore, this.oauth, this.metadataStore, this.extentStore, this.logger, this.loose),
pageBlobHandler: new PageBlobHandler_1.default(this.metadataStore, this.extentStore, this.logger, this.loose, new PageBlobRangesManager_1.default()),
serviceHandler: new ServiceHandler_1.default(this.accountDataStore, this.oauth, this.metadataStore, this.extentStore, this.logger, this.loose)
};
const handlerMiddlewareFactory = new HandlerMiddlewareFactory_1.default(this.handlers, this.logger);
const subRequestHandlerMiddleware = (req, res, locals, next) => {
handlerMiddlewareFactory.createHandlerMiddleware()(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), next);
};
const subRequestSerializeMiddleWare = (req, res, locals, next) => {
(0, serializer_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), res, next, this.logger);
};
const subRequestErrorMiddleWare = (err, req, res, locals, next) => {
(0, error_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), err, req, res, next, this.logger);
};
const subRequestEndMiddleWare = (req, res, locals, next) => {
(0, end_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), res, this.logger);
next();
};
this.handlePipeline = [
subRequestContextMiddleware,
subRequestDispatchMiddleware,
subRequestAuthenticationMiddleware,
subRequestDeserializeMiddleware,
subRequestHandlerMiddleware,
subRequestSerializeMiddleWare,
subRequestEndMiddleWare
];
this.operationFinder = [
subRequestContextMiddleware,
subRequestDispatchMiddleware,
];
this.errorHandler = subRequestErrorMiddleWare;
}
async streamToBuffer2(stream, buffer, encoding) {
let pos = 0; // Position in stream
const bufferSize = buffer.length;
return new Promise((resolve, reject) => {
stream.on("readable", () => {
let chunk = stream.read();
if (!chunk) {
return;
}
if (typeof chunk === "string") {
chunk = Buffer.from(chunk, encoding);
}
if (pos + chunk.length > bufferSize) {
reject(new Error(`Stream exceeds buffer size. Buffer size: ${bufferSize}`));
return;
}
buffer.fill(chunk, pos, pos + chunk.length);
pos += chunk.length;
});
stream.on("end", () => {
resolve(pos);
});
stream.on("error", reject);
});
}
async requestBodyToString(body) {
let buffer = Buffer.alloc(4 * 1024 * 1024);
const responseLength = await this.streamToBuffer2(body, buffer);
// Slice the buffer to trim the empty ending.
buffer = buffer.slice(0, responseLength);
return buffer.toString();
}
async getSubRequestOperation(request) {
const subRequestHandlePipeline = this.operationFinder;
const fakeResponse = new BlobBatchSubResponse_1.BlobBatchSubResponse(0, "HTTP/1.1");
return new Promise((resolve, reject) => {
const locals = {};
let i = 0;
const next = (error) => {
if (error) {
reject(error);
}
else {
++i;
if (i < subRequestHandlePipeline.length) {
subRequestHandlePipeline[i](request, fakeResponse, locals, next);
}
else {
resolve((new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, request, fakeResponse)).operation);
}
}
};
subRequestHandlePipeline[i](request, fakeResponse, locals, next);
});
}
async parseSubRequests(commonRequestId, perRequestPrefix, batchRequestEnding, subRequestPathPrefix, request, body) {
const requestAll = body.split(batchRequestEnding);
const response1 = requestAll[0]; // string after ending is useless
const response2 = response1.split(perRequestPrefix);
const subRequests = response2.slice(1);
const blobBatchSubRequests = [];
let previousOperation;
for (const subRequest of subRequests) {
const requestLines = subRequest.split(`${constants_1.HTTP_LINE_ENDING}`);
// Content-Encoding
// Content-Type
// Content-ID
// empty line
// Operation infos
if (requestLines.length < 5)
throw new Error("Bad request");
// Get Content_ID
let lineIndex = 0;
let content_id;
while (lineIndex < requestLines.length) {
if (requestLines[lineIndex] === '')
break;
const header = requestLines[lineIndex].split(constants_1.HTTP_HEADER_DELIMITER, 2);
if (header.length !== 2)
throw new Error("Bad Request");
if (header[0].toLocaleLowerCase() === "content-id") {
content_id = parseInt(header[1], 10);
}
++lineIndex;
}
if (content_id === undefined)
throw new Error("Bad request");
// "DELETE /container166063791875402779/blob0 HTTP/1.1"
++lineIndex;
const operationInfos = requestLines[lineIndex].split(" ");
if (operationInfos.length < 3)
throw new Error("Bad request");
const requestPath = operationInfos[1].startsWith("/") ? operationInfos[1] : "/" + operationInfos[1];
if (!requestPath.startsWith(subRequestPathPrefix)) {
throw new Error("Request from a different container");
}
const url = `${request.getEndpoint()}${requestPath}`;
const method = operationInfos[0];
const blobBatchSubRequest = new BlobBatchSubRequest_1.BlobBatchSubRequest(content_id, url, method, operationInfos[2], {});
++lineIndex;
while (lineIndex < requestLines.length) {
if (requestLines[lineIndex] === '')
break; // Last line
const header = requestLines[lineIndex].split(constants_1.HTTP_HEADER_DELIMITER, 2);
if (header.length !== 2)
throw new Error("Bad Request");
blobBatchSubRequest.setHeader(header[0], header[1]);
++lineIndex;
}
const operation = await this.getSubRequestOperation(blobBatchSubRequest);
if (operation !== operation_1.default.Blob_Delete && operation !== operation_1.default.Blob_SetTier) {
throw new Error("Not supported operation");
}
if (previousOperation === undefined) {
previousOperation = operation;
}
else if (operation !== previousOperation) {
throw new StorageError_1.default(400, "AllBatchSubRequestsShouldBeSameApi", "All batch subrequests should be the same api.", commonRequestId);
}
blobBatchSubRequests.push(blobBatchSubRequest);
}
if (blobBatchSubRequests.length === 0) {
throw new Error("Bad Request");
}
return blobBatchSubRequests;
}
serializeSubResponse(subResponsePrefix, responseEnding, subResponses) {
let responseBody = "";
subResponses.forEach(subResponse => {
responseBody += subResponsePrefix,
responseBody += "Content-Type: application/http" + constants_1.HTTP_LINE_ENDING;
if (subResponse.content_id !== undefined) {
responseBody += "Content-ID" + constants_1.HTTP_HEADER_DELIMITER + subResponse.content_id.toString() + constants_1.HTTP_LINE_ENDING;
}
responseBody += constants_1.HTTP_LINE_ENDING;
responseBody += subResponse.protocolWithVersion + " " + subResponse.getStatusCode().toString() + " "
+ subResponse.getStatusMessage() + constants_1.HTTP_LINE_ENDING;
const headers = subResponse.getHeaders();
for (const key of Object.keys(headers)) {
responseBody += key + constants_1.HTTP_HEADER_DELIMITER + headers[key] + constants_1.HTTP_LINE_ENDING;
}
const bodyContent = subResponse.getBodyContent();
if (bodyContent !== "") {
responseBody += constants_1.HTTP_LINE_ENDING + bodyContent + constants_1.HTTP_LINE_ENDING;
}
responseBody += constants_1.HTTP_LINE_ENDING;
});
responseBody += responseEnding;
return responseBody;
}
async submitBatch(body, requestBatchBoundary, subRequestPathPrefix, batchRequest, context) {
const perRequestPrefix = `--${requestBatchBoundary}${constants_1.HTTP_LINE_ENDING}`;
const batchRequestEnding = `--${requestBatchBoundary}--`;
const requestBody = await this.requestBodyToString(body);
let subRequests;
let error;
try {
subRequests = await this.parseSubRequests(context.contextId, perRequestPrefix, batchRequestEnding, subRequestPathPrefix, batchRequest, requestBody);
}
catch (err) {
if ((err instanceof MiddlewareError_1.default)
&& err.hasOwnProperty("storageErrorCode")
&& err.hasOwnProperty("storageErrorMessage")
&& err.hasOwnProperty("storageRequestID")) {
error = err;
}
else {
error = new StorageError_1.default(400, "InvalidInput", "One of the request inputs is not valid.", context.contextId);
}
}
const subResponses = [];
if (subRequests && subRequests.length > 256) {
error = new StorageError_1.default(400, "ExceedsMaxBatchRequestCount", "The batch operation exceeds maximum number of allowed subrequests.", context.contextId);
}
if (error) {
this.logger.error(`BlobBatchHandler: ${error.message}`, context.contextId);
const errorResponse = new BlobBatchSubResponse_1.BlobBatchSubResponse(undefined, "HTTP/1.1");
await this.HandleOneFailedRequest(error, batchRequest, errorResponse);
subResponses.push(errorResponse);
}
else {
for (const subRequest of subRequests) {
this.logger.info(`BlobBatchHandler: starting on subrequest ${subRequest.content_id}`, context.contextId);
const subResponse = new BlobBatchSubResponse_1.BlobBatchSubResponse(subRequest.content_id, subRequest.protocolWithVersion);
await this.HandleOneSubRequest(subRequest, subResponse);
subResponses.push(subResponse);
this.logger.info(`BlobBatchHandler: completed on subrequest ${subRequest.content_id} ${subResponse.getHeader("x-ms-request-id")}`, context.contextId);
}
}
return this.serializeSubResponse(perRequestPrefix, batchRequestEnding, subResponses);
}
HandleOneSubRequest(request, response) {
const subRequestHandlePipeline = this.handlePipeline;
const subRequestErrorHandler = this.errorHandler;
let completed = false;
return new Promise((resolve, reject) => {
const locals = {};
let i = 0;
const next = (error) => {
if (completed) {
resolve();
return;
}
if (error) {
subRequestErrorHandler(error, request, response, locals, next);
completed = true;
}
else {
++i;
if (i < subRequestHandlePipeline.length) {
subRequestHandlePipeline[i](request, response, locals, next);
}
else {
resolve();
}
}
};
subRequestHandlePipeline[i](request, response, locals, next);
});
}
HandleOneFailedRequest(err, request, response) {
const subRequestErrorHandler = this.errorHandler;
return new Promise((resolve, reject) => {
subRequestErrorHandler(err, request, response, {}, resolve);
});
}
}
exports.BlobBatchHandler = BlobBatchHandler;
//# sourceMappingURL=BlobBatchHandler.js.map