1 |
|
2 |
|
3 | import { HttpHeaders } from "@azure/core-http";
|
4 | import { HTTP_VERSION_1_1, HTTP_LINE_ENDING, HeaderConstants, HTTPURLConnection, } from "./utils/constants";
|
5 | import { getBodyAsText } from "./BatchUtils";
|
6 | import { logger } from "./log";
|
7 | const HTTP_HEADER_DELIMITER = ": ";
|
8 | const SPACE_DELIMITER = " ";
|
9 | const NOT_FOUND = -1;
|
10 |
|
11 |
|
12 |
|
13 | export class BatchResponseParser {
|
14 | constructor(batchResponse, subRequests) {
|
15 | if (!batchResponse || !batchResponse.contentType) {
|
16 |
|
17 | throw new RangeError("batchResponse is malformed or doesn't contain valid content-type.");
|
18 | }
|
19 | if (!subRequests || subRequests.size === 0) {
|
20 |
|
21 | throw new RangeError("Invalid state: subRequests is not provided or size is 0.");
|
22 | }
|
23 | this.batchResponse = batchResponse;
|
24 | this.subRequests = subRequests;
|
25 | this.responseBatchBoundary = this.batchResponse.contentType.split("=")[1];
|
26 | this.perResponsePrefix = `--${this.responseBatchBoundary}${HTTP_LINE_ENDING}`;
|
27 | this.batchResponseEnding = `--${this.responseBatchBoundary}--`;
|
28 | }
|
29 |
|
30 | async parseBatchResponse() {
|
31 |
|
32 |
|
33 | if (this.batchResponse._response.status !== HTTPURLConnection.HTTP_ACCEPTED) {
|
34 | throw new Error(`Invalid state: batch request failed with status: '${this.batchResponse._response.status}'.`);
|
35 | }
|
36 | const responseBodyAsText = await getBodyAsText(this.batchResponse);
|
37 | const subResponses = responseBodyAsText
|
38 | .split(this.batchResponseEnding)[0]
|
39 | .split(this.perResponsePrefix)
|
40 | .slice(1);
|
41 | const subResponseCount = subResponses.length;
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | if (subResponseCount !== this.subRequests.size && subResponseCount !== 1) {
|
47 | throw new Error("Invalid state: sub responses' count is not equal to sub requests' count.");
|
48 | }
|
49 | const deserializedSubResponses = new Array(subResponseCount);
|
50 | let subResponsesSucceededCount = 0;
|
51 | let subResponsesFailedCount = 0;
|
52 |
|
53 | for (let index = 0; index < subResponseCount; index++) {
|
54 | const subResponse = subResponses[index];
|
55 | const deserializedSubResponse = {};
|
56 | deserializedSubResponse.headers = new HttpHeaders();
|
57 | const responseLines = subResponse.split(`${HTTP_LINE_ENDING}`);
|
58 | let subRespHeaderStartFound = false;
|
59 | let subRespHeaderEndFound = false;
|
60 | let subRespFailed = false;
|
61 | let contentId = NOT_FOUND;
|
62 | for (const responseLine of responseLines) {
|
63 | if (!subRespHeaderStartFound) {
|
64 |
|
65 | if (responseLine.startsWith(HeaderConstants.CONTENT_ID)) {
|
66 | contentId = parseInt(responseLine.split(HTTP_HEADER_DELIMITER)[1]);
|
67 | }
|
68 |
|
69 |
|
70 | if (responseLine.startsWith(HTTP_VERSION_1_1)) {
|
71 | subRespHeaderStartFound = true;
|
72 | const tokens = responseLine.split(SPACE_DELIMITER);
|
73 | deserializedSubResponse.status = parseInt(tokens[1]);
|
74 | deserializedSubResponse.statusMessage = tokens.slice(2).join(SPACE_DELIMITER);
|
75 | }
|
76 | continue;
|
77 | }
|
78 | if (responseLine.trim() === "") {
|
79 |
|
80 | if (!subRespHeaderEndFound) {
|
81 | subRespHeaderEndFound = true;
|
82 | }
|
83 | continue;
|
84 | }
|
85 |
|
86 | if (!subRespHeaderEndFound) {
|
87 | if (responseLine.indexOf(HTTP_HEADER_DELIMITER) === -1) {
|
88 |
|
89 | throw new Error(`Invalid state: find non-empty line '${responseLine}' without HTTP header delimiter '${HTTP_HEADER_DELIMITER}'.`);
|
90 | }
|
91 |
|
92 | const tokens = responseLine.split(HTTP_HEADER_DELIMITER);
|
93 | deserializedSubResponse.headers.set(tokens[0], tokens[1]);
|
94 | if (tokens[0] === HeaderConstants.X_MS_ERROR_CODE) {
|
95 | deserializedSubResponse.errorCode = tokens[1];
|
96 | subRespFailed = true;
|
97 | }
|
98 | }
|
99 | else {
|
100 |
|
101 | if (!deserializedSubResponse.bodyAsText) {
|
102 | deserializedSubResponse.bodyAsText = "";
|
103 | }
|
104 | deserializedSubResponse.bodyAsText += responseLine;
|
105 | }
|
106 | }
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | if (contentId !== NOT_FOUND &&
|
112 | Number.isInteger(contentId) &&
|
113 | contentId >= 0 &&
|
114 | contentId < this.subRequests.size &&
|
115 | deserializedSubResponses[contentId] === undefined) {
|
116 | deserializedSubResponse._request = this.subRequests.get(contentId);
|
117 | deserializedSubResponses[contentId] = deserializedSubResponse;
|
118 | }
|
119 | else {
|
120 | logger.error(`subResponses[${index}] is dropped as the Content-ID is not found or invalid, Content-ID: ${contentId}`);
|
121 | }
|
122 | if (subRespFailed) {
|
123 | subResponsesFailedCount++;
|
124 | }
|
125 | else {
|
126 | subResponsesSucceededCount++;
|
127 | }
|
128 | }
|
129 | return {
|
130 | subResponses: deserializedSubResponses,
|
131 | subResponsesSucceededCount: subResponsesSucceededCount,
|
132 | subResponsesFailedCount: subResponsesFailedCount,
|
133 | };
|
134 | }
|
135 | }
|
136 |
|
\ | No newline at end of file |