UNPKG

11.5 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright 2022 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18Object.defineProperty(exports, "__esModule", { value: true });
19exports.LoadBalancingCall = void 0;
20const connectivity_state_1 = require("./connectivity-state");
21const constants_1 = require("./constants");
22const deadline_1 = require("./deadline");
23const metadata_1 = require("./metadata");
24const picker_1 = require("./picker");
25const uri_parser_1 = require("./uri-parser");
26const logging = require("./logging");
27const control_plane_status_1 = require("./control-plane-status");
28const http2 = require("http2");
29const TRACER_NAME = 'load_balancing_call';
30class LoadBalancingCall {
31 constructor(channel, callConfig, methodName, host, credentials, deadline, callNumber) {
32 var _a, _b;
33 this.channel = channel;
34 this.callConfig = callConfig;
35 this.methodName = methodName;
36 this.host = host;
37 this.credentials = credentials;
38 this.deadline = deadline;
39 this.callNumber = callNumber;
40 this.child = null;
41 this.readPending = false;
42 this.pendingMessage = null;
43 this.pendingHalfClose = false;
44 this.pendingChildStatus = null;
45 this.ended = false;
46 this.metadata = null;
47 this.listener = null;
48 this.onCallEnded = null;
49 const splitPath = this.methodName.split('/');
50 let serviceName = '';
51 /* The standard path format is "/{serviceName}/{methodName}", so if we split
52 * by '/', the first item should be empty and the second should be the
53 * service name */
54 if (splitPath.length >= 2) {
55 serviceName = splitPath[1];
56 }
57 const hostname = (_b = (_a = (0, uri_parser_1.splitHostPort)(this.host)) === null || _a === void 0 ? void 0 : _a.host) !== null && _b !== void 0 ? _b : 'localhost';
58 /* Currently, call credentials are only allowed on HTTPS connections, so we
59 * can assume that the scheme is "https" */
60 this.serviceUrl = `https://${hostname}/${serviceName}`;
61 }
62 trace(text) {
63 logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '[' + this.callNumber + '] ' + text);
64 }
65 outputStatus(status, progress) {
66 var _a, _b;
67 if (!this.ended) {
68 this.ended = true;
69 this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"');
70 const finalStatus = Object.assign(Object.assign({}, status), { progress });
71 (_a = this.listener) === null || _a === void 0 ? void 0 : _a.onReceiveStatus(finalStatus);
72 (_b = this.onCallEnded) === null || _b === void 0 ? void 0 : _b.call(this, finalStatus.code);
73 }
74 }
75 doPick() {
76 var _a, _b;
77 if (this.ended) {
78 return;
79 }
80 if (!this.metadata) {
81 throw new Error('doPick called before start');
82 }
83 this.trace('Pick called');
84 const pickResult = this.channel.doPick(this.metadata, this.callConfig.pickInformation);
85 const subchannelString = pickResult.subchannel ?
86 '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() :
87 '' + pickResult.subchannel;
88 this.trace('Pick result: ' +
89 picker_1.PickResultType[pickResult.pickResultType] +
90 ' subchannel: ' +
91 subchannelString +
92 ' status: ' +
93 ((_a = pickResult.status) === null || _a === void 0 ? void 0 : _a.code) +
94 ' ' +
95 ((_b = pickResult.status) === null || _b === void 0 ? void 0 : _b.details));
96 switch (pickResult.pickResultType) {
97 case picker_1.PickResultType.COMPLETE:
98 this.credentials.generateMetadata({ service_url: this.serviceUrl }).then((credsMetadata) => {
99 var _a, _b, _c;
100 const finalMetadata = this.metadata.clone();
101 finalMetadata.merge(credsMetadata);
102 if (finalMetadata.get('authorization').length > 1) {
103 this.outputStatus({
104 code: constants_1.Status.INTERNAL,
105 details: '"authorization" metadata cannot have multiple values',
106 metadata: new metadata_1.Metadata()
107 }, 'PROCESSED');
108 }
109 if (pickResult.subchannel.getConnectivityState() !== connectivity_state_1.ConnectivityState.READY) {
110 this.trace('Picked subchannel ' +
111 subchannelString +
112 ' has state ' +
113 connectivity_state_1.ConnectivityState[pickResult.subchannel.getConnectivityState()] +
114 ' after getting credentials metadata. Retrying pick');
115 this.doPick();
116 return;
117 }
118 if (this.deadline !== Infinity) {
119 finalMetadata.set('grpc-timeout', (0, deadline_1.getDeadlineTimeoutString)(this.deadline));
120 }
121 try {
122 this.child = pickResult.subchannel.getRealSubchannel().createCall(finalMetadata, this.host, this.methodName, {
123 onReceiveMetadata: metadata => {
124 this.trace('Received metadata');
125 this.listener.onReceiveMetadata(metadata);
126 },
127 onReceiveMessage: message => {
128 this.trace('Received message');
129 this.listener.onReceiveMessage(message);
130 },
131 onReceiveStatus: status => {
132 this.trace('Received status');
133 if (status.rstCode === http2.constants.NGHTTP2_REFUSED_STREAM) {
134 this.outputStatus(status, 'REFUSED');
135 }
136 else {
137 this.outputStatus(status, 'PROCESSED');
138 }
139 }
140 });
141 }
142 catch (error) {
143 this.trace('Failed to start call on picked subchannel ' +
144 subchannelString +
145 ' with error ' +
146 error.message);
147 this.outputStatus({
148 code: constants_1.Status.INTERNAL,
149 details: 'Failed to start HTTP/2 stream with error ' + error.message,
150 metadata: new metadata_1.Metadata()
151 }, 'NOT_STARTED');
152 return;
153 }
154 (_b = (_a = this.callConfig).onCommitted) === null || _b === void 0 ? void 0 : _b.call(_a);
155 (_c = pickResult.onCallStarted) === null || _c === void 0 ? void 0 : _c.call(pickResult);
156 this.onCallEnded = pickResult.onCallEnded;
157 this.trace('Created child call [' + this.child.getCallNumber() + ']');
158 if (this.readPending) {
159 this.child.startRead();
160 }
161 if (this.pendingMessage) {
162 this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message);
163 }
164 if (this.pendingHalfClose) {
165 this.child.halfClose();
166 }
167 }, (error) => {
168 // We assume the error code isn't 0 (Status.OK)
169 const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(typeof error.code === 'number' ? error.code : constants_1.Status.UNKNOWN, `Getting metadata from plugin failed with error: ${error.message}`);
170 this.outputStatus({
171 code: code,
172 details: details,
173 metadata: new metadata_1.Metadata()
174 }, 'PROCESSED');
175 });
176 break;
177 case picker_1.PickResultType.DROP:
178 const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(pickResult.status.code, pickResult.status.details);
179 this.outputStatus({ code, details, metadata: pickResult.status.metadata }, 'DROP');
180 break;
181 case picker_1.PickResultType.TRANSIENT_FAILURE:
182 if (this.metadata.getOptions().waitForReady) {
183 this.channel.queueCallForPick(this);
184 }
185 else {
186 const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(pickResult.status.code, pickResult.status.details);
187 this.outputStatus({ code, details, metadata: pickResult.status.metadata }, 'PROCESSED');
188 }
189 break;
190 case picker_1.PickResultType.QUEUE:
191 this.channel.queueCallForPick(this);
192 }
193 }
194 cancelWithStatus(status, details) {
195 var _a;
196 this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"');
197 (_a = this.child) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(status, details);
198 this.outputStatus({ code: status, details: details, metadata: new metadata_1.Metadata() }, 'PROCESSED');
199 }
200 getPeer() {
201 var _a, _b;
202 return (_b = (_a = this.child) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : this.channel.getTarget();
203 }
204 start(metadata, listener) {
205 this.trace('start called');
206 this.listener = listener;
207 this.metadata = metadata;
208 this.doPick();
209 }
210 sendMessageWithContext(context, message) {
211 this.trace('write() called with message of length ' + message.length);
212 if (this.child) {
213 this.child.sendMessageWithContext(context, message);
214 }
215 else {
216 this.pendingMessage = { context, message };
217 }
218 }
219 startRead() {
220 this.trace('startRead called');
221 if (this.child) {
222 this.child.startRead();
223 }
224 else {
225 this.readPending = true;
226 }
227 }
228 halfClose() {
229 this.trace('halfClose called');
230 if (this.child) {
231 this.child.halfClose();
232 }
233 else {
234 this.pendingHalfClose = true;
235 }
236 }
237 setCredentials(credentials) {
238 throw new Error("Method not implemented.");
239 }
240 getCallNumber() {
241 return this.callNumber;
242 }
243}
244exports.LoadBalancingCall = LoadBalancingCall;
245//# sourceMappingURL=load-balancing-call.js.map
\No newline at end of file