Skip to content

Instantly share code, notes, and snippets.

@jeremymeng
Last active February 17, 2022 22:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremymeng/36c8162b65fe8945c8dae59061d9ce1d to your computer and use it in GitHub Desktop.
Save jeremymeng/36c8162b65fe8945c8dae59061d9ce1d to your computer and use it in GitHub Desktop.
workaround for @azure/blob-storage metadata from getProperties() casing issue
import {
BlobServiceClient,
ContainerClient,
StorageSharedKeyCredential,
} from "@azure/storage-blob";
import { ExampleHttpClient } from "./exampleHttpClient.js";
import * as dotenv from "dotenv";
dotenv.config();
async function main() {
const accountName = process.env.ACCOUNT_NAME || "<account name>";
const accountKey = process.env.ACCOUNT_KEY || "<account key>";
const containerName = process.env.CONTAINER_NAME || "<container name>";
const sharedKeyCredential = new StorageSharedKeyCredential(
accountName,
accountKey
);
const url = `https://${accountName}.blob.core.windows.net/`;
const serviceClient = new BlobServiceClient(url, sharedKeyCredential);
const containerClient = serviceClient.getContainerClient(containerName);
const properties1 = await containerClient.getProperties();
console.log("metadata retrieved:");
console.log(properties1.metadata);
const getPropertiesClient = new ContainerClient(
containerClient.url,
sharedKeyCredential,
{
httpClient: new ExampleHttpClient(),
}
);
const properties2 = await getPropertiesClient.getProperties();
console.log("metadata retrieved with custom http client:");
console.log(properties2.metadata);
await containerClient.setMetadata({ ...properties2.metadata, caseSensitiveKey: "camelCaseValue" });
const properties3 = await getPropertiesClient.getProperties();
console.log("metadata retrieved with custom http client after setting metadata:");
console.log(properties3.metadata);
}
main().catch((e) => {
console.log("error occurred");
console.log(e);
});
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/**
* @summary This sample demonstrates how to implement a custom HttpClient.
*/
import * as http from "http";
import * as https from "https";
import {
HttpClient,
HttpHeaders,
HttpHeadersLike,
HttpOperationResponse,
RestError,
WebResourceLike,
} from "@azure/core-http";
/**
* A custom HttpClient that uses `https` to send request and preserve
* the casing of 'x-ms-*' http header names.
* NOTE: this is for demostrantion purpose only and is not a complete implementation.
*/
export class ExampleHttpClient implements HttpClient {
private httpsKeepAliveAgent?: https.Agent;
public async sendRequest(
httpRequest: WebResourceLike
): Promise<HttpOperationResponse> {
let body = httpRequest.body
? typeof httpRequest.body === "function"
? httpRequest.body()
: httpRequest.body
: undefined;
const result = await new Promise<HttpOperationResponse>(
(resolve, reject) => {
const req = this.makeRequest(httpRequest, async (res) => {
const status = res.statusCode ?? 0;
const httpHeaders = parseHeaders(res.rawHeaders);
const response: HttpOperationResponse = {
status,
headers: httpHeaders,
request: httpRequest,
};
response.bodyAsText = await streamToText(res);
resolve(response);
});
req.on("error", (err) => {
reject(
new RestError(
err.message,
RestError.REQUEST_SEND_ERROR,
undefined,
httpRequest
)
);
});
if (body && isReadableStream(body)) {
body.pipe(req);
} else if (body) {
req.end(body);
} else {
// streams don't like "undefined" being passed as data
req.end();
}
}
);
return result;
}
private makeRequest(
request: WebResourceLike,
callback: (res: http.IncomingMessage) => void
): http.ClientRequest {
const url = new URL(request.url);
const agent = this.getOrCreateAgent(request?.keepAlive ?? true);
const realHeaders = request.headers.rawHeaders();
const options: http.RequestOptions = {
agent,
hostname: url.hostname,
path: `${url.pathname}${url.search}`,
port: url.port,
method: request.method,
headers: realHeaders,
};
return https.request(options, callback);
}
private getOrCreateAgent(keepAlive: boolean): https.Agent {
if (keepAlive) {
if (!this.httpsKeepAliveAgent) {
this.httpsKeepAliveAgent = new https.Agent({
keepAlive: true,
});
}
return this.httpsKeepAliveAgent;
} else {
return https.globalAgent;
}
}
}
function streamToText(stream: NodeJS.ReadableStream): Promise<string> {
return new Promise<string>((resolve, reject) => {
const buffer: Buffer[] = [];
stream.on("data", (chunk) => {
if (Buffer.isBuffer(chunk)) {
buffer.push(chunk);
} else {
buffer.push(Buffer.from(chunk));
}
});
stream.on("end", () => {
resolve(Buffer.concat(buffer).toString("utf8"));
});
stream.on("error", (e) => {
reject(
new RestError(
`Error reading response as text: ${e.message}`,
RestError.PARSE_ERROR
)
);
});
});
}
export function parseHeaders(
rawHeaders: string[]
): HttpHeadersLike {
const httpHeaders = new HttpHeaders();
for (let i = 0; i < rawHeaders.length; i += 2) {
httpHeaders.set(rawHeaders[i], rawHeaders[i + 1]);
}
return httpHeaders;
}
function isReadableStream(body: any): body is NodeJS.ReadableStream {
return body && typeof body.pipe === "function";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment