www.gusucode.com > external 工具箱matlab源码程序 > external/interfaces/webservices/http/+matlab/+net/+http/RequestMessage.m
classdef (Sealed) RequestMessage < matlab.net.http.Message & matlab.mixin.CustomDisplay % RequestMessage An HTTP request message % To prepare a message, construct a RequestMessage and specify optional % Method, Header and Body properties. You can then use send to SEND the % message, or COMPLETE to validate the message prior to sending. These % functions will fill in any necessary Header fields and other properties of % the message that you have not set. % % RequestMessage methods: % RequestMessage - constructor % send - send a message and receive a response % complete - validate and complete a message % string - return the contents of the message as a string % char - return the contents of the message as a character vector % show - return/display contents with optional maximum length % % RequestMessage properties: % RequestLine - Request line of the message % Method - Same as RequestLine.Method % Header - Header of the message % Body - Body of the message % Completed - true if message was completed % % Examples: % import matlab.net.http.RequestMessage % req = RequestMessage; % resp = req.send('http://www.mathworks.com'); % html = resp.Body.Data; % returns HTML text % % Note that for the simple case above, which issues a GET request with no special % headers, it is easier to use webread. % % req = RequestMessage('delete'); % resp = req.send('http://www.mathworks.com') % % resp = % % ResponseMessage with properties: % % StatusLine: 'HTTP/1.1 405 Http method DELETE is not supported by this URL' % StatusCode: MethodNotAllowed % Header: [1x9 matlab.net.http.HeaderField] % Body: [1x1 matlab.net.http.MessageBody] % Completed: 0 % % See also ResponseMessage, webread % Copyright 2015-2016 The MathWorks, Inc. properties (Dependent) % Method - a matlab.net.http.RequestMethod % This is a dependent property equal to RequestLine.Method. Default is % RequestMethod.GET. If you are sending data to a server (i.e., the Body % is not empty) you would specify a method such as RequestMethod.PUT or % RequestMethod.POST. If you set this property to a string, it will be % converted to a RequestMethod. % % See also RequestMethod, RequestLine, send, complete, Body Method % RequestMethod or empty % RequestLine - first line of an HTTP message % This is a matlab.net.http.RequestLine that contains the method, target % and protocol version. This line is automatically created when you send a % message based on the method and URI you specify, but if you set this % explicitly then its contents will be used as the request line. The value % may be set to a RequestLine object or a string, which is parsed and % converted to a RequestLine object. % % See also matlab.net.http.RequestLine RequestLine end properties (Constant, Access=private) Properties = {'matlab.net.http.RequestMethod', 'Method', ... 'matlab.net.http.Header', 'Header', 'matlab.net.http.Body', 'Body'} % TBD this should be much longer, but it's of no big consequence really DisallowedFields = {'Location','Retry-After','Vary','Etag',... 'Last-Modified','Set-Cookie','Accept-Ranges','Allow','Server',... 'WWW-Authenticate','Proxy-Authenticate','Authentication-Info'} end methods function obj = set.Method(obj, value) if isempty(obj.RequestLine) obj.RequestLine = matlab.net.http.RequestLine(); end if isempty(value) value = matlab.net.http.RequestMethod.empty; end obj.RequestLine.Method = value; end function value = get.Method(obj) if isempty(obj.RequestLine) value = []; else value = obj.RequestLine.Method; end end function obj = set.RequestLine(obj, value) if isempty(value) obj.StartLine = []; else validateattributes(value, {'matlab.net.http.RequestLine'}, {'scalar'}, mfilename, 'RequestLine'); obj.StartLine = value; end end function value = get.RequestLine(obj) value = obj.StartLine; end function obj = RequestMessage(method, header, body) % RequestMessage creates an HTTP request message % REQUEST = RequestMessage(METHOD,HEADER,BODY) creates a RequestMessage % with the specified properties. All parameters are optional and [] may be % for placeholders. % % METHOD The request method or request line: % matlab.net.http.RequestLine - the entire request line % matlab.net.http.RequestMethod or string - the request method % This sets the RequestLine or Method properties. % % In most cases you need only specify the request method, which % is part of the request line, but you may want to specify the % entire request line if you need control over its contents. For % example, to explicitly send a message to a proxy that should be % forwarded to a server, instead of letting MATLAB choose the % proxy based on your proxy settings, you need to set the % RequestTarget property in the RequestLine to the full URI, % because the send method would normally set it to just the Path % portion of the URI. % % When you send or complete a message, the deault METHOD is % RequestMethod.GET. % % HEADER The header fields: vector of HeaderField. This sets the Header % property. Default is an empty header, but several header fields % are created by default when you send or complete a message. % % BODY The message body: MessageBody or any data acceptable to the % MessageBody constructor. This sets the Body property. Normally % a RequestMessage with a Body uses a method such as 'PUT' or % 'POST', not the default 'GET', though this is not enforced. % % See also RequestLine, RequestMethod, MessageBody, Method, Header, Body, % HeaderField, send, complete import matlab.net.internal.*; if nargin > 0 if ~isempty(method) if ischar(method) obj.Method = matlab.net.http.RequestMethod.( ... char(upper(getString(method, mfilename, 'method')))); elseif isa(method, 'matlab.net.http.RequestMethod') obj.Method = method; else validateattributes(method, {'matlab.net.http.RequestLine', ... 'matlab.net.http.RequestMethod', 'char', 'string'}, ... {'scalar'}, mfilename, 'METHOD'); obj.RequestLine = method; end end if nargin > 1 if ~isempty(header) validateattributes(header, {'matlab.net.http.HeaderField'}, ... {'vector'}, mfilename, 'HEADER'); obj.Header = header; end if nargin > 2 if ~isempty(body) obj.Body = body; end end end end end function [response, request, history] = send(obj, uri, options) % SEND Send an HTTP request % [RESPONSE, COMPLETEDREQUEST, HISTORY] = SEND(REQUEST, URI, OPTIONS) % send the REQUEST to the Web service specified by URI and returns the % response (if any) as RESPONSE. % % REQUEST RequestMessage to be sent. % URI matlab.net.URI or string acceptable to the URI constructor, % the destination of the request. If it is a URI, it must name % a Host. If a string, and it does not mention a Scheme, % 'http://' is added; for example 'www.mathworks.com' and % '//www.mathworks.com' are both assumed to be % 'http://www.mathworks.com'. % OPTIONS (optional) HTTPOptions - additional options for processing % request and response. % RESPONSE The final ResponseMessage received from the server. % COMPLETEDREQUEST The request that was sent prior to receiving the % RESPONSE, after completion by SEND and augmented with any % authentication information or redirection information. % HISTORY A LogRecord vector containing a log of the request and % response messages that were exchanged to satisfy this send % request. In the normal case where there is just a single % request and response, this contains one record. There may be % multiple LogRecords in this history in the case of an % authentication containing multiple messages and for each % redirection. One use of this history, in addition to % debugging, is to obtain all the Set-Cookie headers from % response messages, which you may need to send back in % subsequent requests. When this function returns normally, the % last record in this history always contains the same value as % COMPLETEDREQUEST and RESPONSE (headers only). In general a % LogRecord contains only headers. To log message bodies, % specify OPTIONS.SavePayload. % % By default, SEND verifies the semantic correctness of the headers and % other parts of the message, completes the URI and fills in any % additional header fields needed to create a properly formed request, % and (if REQUEST.Body.Payload is not already set) calls appropriate % conversion functions to convert any REQUEST.Body.Data to a vector of % bytes representing an HTTP payload to be sent. Normally a 'GET' % request does not contain data, but SEND will send the Body regardless of % the RequestMethod. If the server returns data in its response, SEND % converts that data to MATLAB data and saves it in RESPONSE.Body.Data. % See MessageBody.Data for more information on data conversion. % % If the REQUEST.Header already contains a field that SEND would normally % add, SEND verifies that the field has the expected value. This % behavior can be altered in several ways: % % 1. To send a message as is without any checking or alteration of the % header, set REQUEST.Completed to true prior to sending. If you % used the complete method to complete the request, you should % specify the same value of URI and OPTIONS that you provided to % complete, or there may be unpredictable results. Even if % Completed is set, unspecified fields in the RequestLine will be % filled in with default values. % 2. To allow SEND to check and fill in the header, but to suppress % adding a particular header field that SEND might add, add that % field to REQUEST.Header with a value of []. For example, SEND % automatically adds a User-Agent header field. If you do not want % this, add HeaderField('User-Agent') to the header. Header fields % with empty values are not included in the message. The Host and % Connection fields cannot be suppressed. % 3. To override the value that SEND would add for a given header % field, add your own instance of that field before sending or % completing the message. However, for some header field types, % SEND may still reject the message if the value is not valid. To % prevent any checking of the value of a given field, add a % matlab.http.field.GenericField to the header with the desired name % and value. SEND will not add any header fields with names equal % to any GenericField headers and will not check their correctness. % 4. To send raw binary data without conversion, you can insert a uint8 % vector either Body.Data or Body.Payload. The only difference is % that data in Body.Data is subject to conversion based on the % Content-Type field in the message, while Body.Payload is not. Note % that SEND will always try to convert nonempty Body.Data if % Body.Payload is empty, even if Completed is already set. See % MessageBody.Data for conversion rules. % % SEND throws an MException if the message could not be completed because % its headers are not well-formed or conversion of Body.Data fails. If % the message was completed but the Web service cannot be reached, or the % Web service does not respond within the timeout period specified in % OPTIONS, or a conversion error occurs trying to convert the response % payload to MATLAB data, SEND throws an HTTPException. If the Web % service responds and returns an HTTP error status, SEND returns % normally, setting the Status property of RESPONSE to indicate the error % returned from the server. You should always check RESPONSE.Status to % determine whether the request was accepted. % % After SEND returns, you can examine the COMPLETEDREQUEST to see what % was sent, including the converted Data in Body.Payload if % OPTIONS.SavePayload is set. If multiple messages were involved in % accessing the server (for example, there were redirections or an % authentication exchange occurred), it will contain the last such % request. To see the first, or intermediate messages, look at HISTORY. % % Since the COMPLETEDREQUEST is the last request made after all % redirections and authentications, you may need to clear % RequestLine.RequestTarget and possibly some header fields if you want to % send COMPLETEDREQUEST instead of REQUEST. A better strategy, if you % plan to send the same request multiple times, is to send the result of % complete(REQUEST,URI) to the TARGET returned by that method. % % For RESPONSE, COMPLETEDREQUEST, and all messages in HISTORY, the % Completed property will be set only if the message contains no body or % Body.Payload contains the raw data that was sent or received. Normally % the Payload is not preserved unless HTTPOptions.SavePayload is set. % % If you need to send the same message multiple times, and it contains % data that is time-consuming to convert (e.g., a very large JSON % structure) you may wish to use complete to convert the data once (which % converts Body.Data into Body.Payload) and send the completed request. % % See also RequestMessage, matlab.net.URI, HTTPOptions, ResponseMessage, % matlab.net.http.field.GenericField, HTTPException, MException, % MessageBody, Completed, complete, LogRecord import matlab.net.http.* import matlab.net.internal.* import matlab.net.* import matlab.net.http.internal.* if nargin < 3 options = HTTPOptions; else validateattributes(options, {'matlab.net.http.HTTPOptions'}, {'scalar'}, ... mfilename, 'OPTIONS'); end history = LogRecord.empty; if options.UseProxy % if proxy is to be used and there's none in options, get proxy % information from preferences or the system proxy if isempty(options.ProxyURI) [proxyURI, username, password] = getProxySettings(createURIFromInput(uri)); if ~isempty(proxyURI) options.ProxyURI = proxyURI; if ~isempty(username) % If preferences contains a username and password for the % proxy, we need to add that information to the % Credentials array in options (or make sure it's already % there), so that we'll find it when we try to access the % proxy. options = updateProxyCredentials(options, proxyURI, ... username, password); end end end else options.ProxyURI = []; end if obj.Completed if isempty(obj.Method) error(message('MATLAB:http:MissingMethod')); end obj = obj.convertData(false); % If a message is already completed, then the uri should be consistent with % the RequestLine.RequestTarget: if the RequestTarget is absolute then assume % the uri is a proxy and ignore all proxy settings in options. If target is relative, % then assume uri is not a proxy. In this case, if there is a proxy setting, % then it the protocol must be https. [completedURI, ~] = createURIFromInput(uri); if ~isempty(obj.RequestLine) [completedURI, proxyURI] = obj.adjustForProxy(completedURI, options.ProxyURI); options.ProxyURI = proxyURI; try % Use RequestLine.finish to validate; this throws if the RequestLine has any % filled-in fields that are not what's expected. This is the same test we make % in the non-completed case (in completeInternal, but instead of erroring out, % just warn). obj.RequestLine.finish(completedURI, ~isempty(options.ProxyURI), ... obj.RequestLine.Method); catch e % Errors in finish() above become just warnings warning(message('MATLAB:http:BadRequestLineMsg', e.message)); end end else % Not completed; always convert data obj = obj.convertData(true); [completedURI, obj] = obj.completeInternal(uri, options); end connector = HTTPConnector(completedURI, options, [], obj.Header); deleteTheConnector = onCleanup(@()delete(connector)); connector.RequestMethod = char(obj.Method); if options.UseProgressMonitor && ~isempty(options.ProgressMonitorFcn) pm = options.ProgressMonitorFcn(); connector.ProgressMonitor = pm; % The error is in the value that was returned by % HTTPOptions.ProgressMonitorFcn, so pretend it happened there. validateattributes(pm, ... {'matlab.net.http.ProgressMonitor'}, {'scalar'}, ... 'HTTPOptions', 'ProgressMonitorFcn'); endProgressMonitor = onCleanup(@pm.done); end % Insert the payload, if any into the connector. The call to complete() % above should have filled in PayloadInt, if any if ~isempty(obj.Body) && ~isempty(obj.Body.Payload) connector.Payload = obj.Body.Payload; end if (isempty(options.ProxyURI) && isempty(options.Credentials)) || ... ~options.Authenticate % don't authenticate [response, request, history] = ... obj.sendOneRequest(connector, options, [], ... ~isempty(options.ProxyURI), history); else % possibly authenticate % TBD (g1283802): This process is flawed because, if both proxy and % server require authentication, we'll ALWAYS send the first message with % only the proxy credentials and then need another message to send the server % credentials. This code needs to be refactored so that we only send 1 % message once we have all the right credentials. My guess is that his isn't % a huge use case, where both the proxy and the server require authentication, % expecially since, for now, we only support Basic and Digest. % % authInfo = [] % proxyAuthInfo = [] % first = true % while true % look for proxy credentials based on proxyAuthInfo and proxyURI % and insert/change Proxy-Authorization header % look for server credentials based on authInfo and URI % and insert/change Authorization header % if ~first && no new credentials found % break % send message % if ProxyAuthenticationRequired returned % proxyAuthInfo = from Proxy-Authenticate header % else % authInfo = from WWW-Authenticate header % else break % first = false % end if ~isempty(options.ProxyURI) % If talking to proxy, first try to authenticate to proxy. Once % we get through, authenticate to server. % TBD If the server and proxy both require authentication, this % will always require a 2nd message. We can optimize this by % sending server credentials in the first message, if we know % them, but this involves some change of logic in % sendAndAuthenticate to look up credentials for both proxyURI % and uri at the same time. [response, request, history] = ... obj.sendAndAuthenticate([], options.ProxyURI, connector, ... options, true, history); % any response other than ProxyAuthenticationRequired says we % either authenticated successfully to the proxy or didn't have % to if response.StatusCode ~= StatusCode.ProxyAuthenticationRequired if response.StatusCode == StatusCode.Unauthorized % got through, but still need to authenticate to server [response, request, history] = ... obj.sendAndAuthenticate(response, completedURI, connector, ... options, false, history); end end else % no proxy involved, just go to server [response, request, history] = ... obj.sendAndAuthenticate([], completedURI, connector, options, ... false, history); end end if nargout < 3 history = []; end end function [obj, target] = complete(obj, uri, varargin) % COMPLETE Complete an HTTP request message % [COMPLETEDREQUEST, TARGET] = COMPLETE(REQUEST, URI, OPTIONS) returns a % copy of the message, performing the same validation and addition of header % fields and conversion of data as the send method (throwing an MException % if validation fails), but does not send the message. Use this function to % determine whether the request would be valid, and to see the request that % would be sent prior to sending it, including any conversion of Body.Data % to Body.Payload. The returned COMPLETEDREQUEST has the Completed property % set to true and is suitable for sending without further alteration or % processing. % % Even if Authenticate is set in OPTIONS (which is the default), the % completed request does not include any authorization header fields that % may subsequently be added for authentication to a server or proxy, since % it may not be possible to determine what the server requires without % sending the message. If you want to see what was finally sent in an % authentication exchange, examine the COMPLETEDREQUEST or HISTORY returned % by the SEND method. % % The URI may be a matlab.net.URI or a string acceptable to the URI % constructor. % % OPTIONS is an optional matlab.net.http.HTTPOptions object. If missing, % a default HTTPOptions is assumed. These options are needed to determine % how to complete and validate the request. % % The TARGET is a URI object that specifies where the message will be sent. % If no proxy is specified in OPTIONS, this is the same as URI with % appropriate properties filled in. If a proxy is specified, this is the % URI of the proxy (which will contain only a Host and Port). % % If you intend to send COMPLETEDREQUEST to avoid the cost of a repeat % validation, send it TARGET instead of URI, using the same OPTIONS. Note % that time-dependent header fields added by send, such as 'Date', will not % be updated when sent again using COMPLETEDREQUEST. % % For the purpose of filling in and validating the Header and RequestLine, % this method ignores the Completed property in REQUEST, so it always % returns a modified COMPLETEDREQUEST, or issues errors, if the REQUEST % could not be completed. You can use this to determine whether a % "manually" completed request is valid. % % If Completed is not already set, this method always converts any Data in % REQUEST.Body and stores the result in COMPLETEDREQUEST.Body.Payload, % overwriting any previous contents of Payload. This means that both Data % and Payload in COMPLETEDREQUEST.Body will be filled in. This is different % from the behavior of send which does not save the Payload unless % HTTPOptions.SavePayload is set. You should take this memory usage and % conversion time into account if the message contains a very large amount % of data. % % To complete a message without converting the data, set Completed before % calling this method. If Completed is already set, this method assumes % that the current value of Payload, even if empty, is the desired one. % But note that send must always convert nonempty Body.Data, even if % Completed is true, if Payload is empty. % % See also send, RequestMessage, send, matlab.net.URI, HTTPOptions, % Completed, MessageBody, Body if ~obj.Completed || (obj.Completed && ~isempty(obj.Body) && isempty(obj.Body.Payload)) % If not completed, or if payload is empty, fill in payload of data based on % ContentType header or derived from the data. wasCompleted = obj.Completed; obj = obj.convertData(true); obj.Completed = wasCompleted; end [~, obj, target] = completeInternal(obj, uri, varargin{:}); matlab.net.http.internal.nargoutWarning(nargout,mfilename,'complete'); end end methods (Access=private) function obj = convertData(obj, recalculate) % If the message contains Data and Payload is not set, convert Data to Payload % based on Content-Type. If recalculate set, and there is a nonempty Data, % always convert Data to Payload, clearing any previous contents of Payload. The % recalculate flag means we came here from complete() when Completed was % not set, where the user might have changed the ContentType of a previously % Completed message to do a different conversion than was originally done. if ~isempty(obj.Body) if isempty(obj.Body.Data) if ~isempty(obj.Body.Payload) && isempty(obj.Body.ContentType) % if Payload set but not Data, and Body.ContentType isn't set, % set it to the value in the header or default binary. contentType = obj.getAssumedContentType(); if isempty(contentType) contentType = matlab.net.http.MediaType('application/octet-stream'); end obj.Body.ContentType = contentType; end else if isempty(obj.Body.Payload) || recalculate % if Payload is empty or recalculate set with a nonempty Data, convert body to % payload. We don't come here if Data is empty because the user might have % just set the Payload to send raw data. contentType = obj.getAssumedContentType(); % This uses the ContentTypeField in the header (if the user set it) % or derives the type from the data. [obj.Body.PayloadInt, obj.Body.ContentType] = ... matlab.net.http.internal.data2payload(obj.Body.Data, ... contentType); obj.Body.PayloadLength = length(obj.Body.PayloadInt); else % if payload is already set, leave payload alone if recalculate not % specified end end end end function contentType = getAssumedContentType(obj) % Return the MediaType from the ContentTypeField in the header. If Completed, % return the value from the first ContentTypeField found; otherwise error out % if there is more than one. Returns [] if there is no such field or its % value is empty. if obj.Completed % If Completed only look at first instance of Content-Type, % because we don't want to error out if there's more than one. contentTypeField = obj.getFields('Content-Type'); if ~isempty(contentTypeField) && ~isscalar(contentTypeField) contentTypeField = contentTypeField(1); end else % this errors out if more than one contentTypeField = obj.getSingleField('Content-Type'); end if isempty(contentTypeField) || isempty(contentTypeField.Value) contentType = []; else contentType = contentTypeField.convert(); end end function [uri, obj, target] = completeInternal(obj, uri, options) % If obj specified, do all steps of complete() except filling in Body.Payload, % which caller is expected to have done if Body.Data is set. If there is a % payload, Body.ContentType should have been set. % If obj not specified, just complete the uri and don't do any validation. % target is the actual destination of the request, same as uri or the proxy import matlab.net.http.field.*; import matlab.net.internal.*; import matlab.net.*; import matlab.net.http.*; persistent noBodyMethods bodyExpectedMethods if isempty(noBodyMethods) noBodyMethods = RequestMethod({'GET','DELETE','HEAD','CONNECT','TRACE'}); bodyExpectedMethods = RequestMethod({'POST','PUT'}); end % remember what user set because some calls we make below turn this off wasCompleted = obj.Completed; [uri, origURI] = createURIFromInput(uri); if nargout == 1 return; end if nargin > 2 validateattributes(options, {'matlab.net.http.HTTPOptions'}, ... {'scalar'}, mfilename, 'OPTIONS'); else options = HTTPOptions; end if isempty(obj.RequestLine) obj.RequestLine = matlab.net.http.RequestLine(); end % Add default method (GET) if necessary, and set Target based on URI if % unset if isempty(obj.Method) obj.Method = RequestMethod.GET; end if options.UseProxy if isempty(options.ProxyURI) % if proxy is to be used and there's none in options, get it from % preferences or the system proxy [proxyURI, ~, ~] = getProxySettings(uri); if isempty(proxyURI) target = uri; else target = proxyURI; end else proxyURI = options.ProxyURI; target = proxyURI; end else proxyURI = []; target = uri; end [uri, proxyURI] = obj.adjustForProxy(uri, proxyURI); obj.RequestLine = obj.RequestLine.finish(uri,~isempty(proxyURI),obj.Method); if ~isempty(obj.Header) % make sure no fields have empty names i = find(cellfun(@isempty, {obj.Header.Name}), 1); if ~isempty(i) error(message('MATLAB:http:EmptyNameForField', obj.Header(i).Value)); end end % Add Host field obj = obj.addIfEmpty(HostField(origURI), 1); % Add User-Agent field unless there is already one obj = obj.addIfEmpty(HeaderField('User-Agent', ['MATLAB/' version]), 3); % Add Date field unless there's already one obj = obj.addIfEmpty(DateField(datetime('now')), 5); % Add Connection field or verify it's already there obj = obj.addOrVerify(ConnectionField('close'), 4); % If there is a payload, make sure Content-Type and Content-Length are % added or correct if isempty(obj.Body) payloadLength = 0; else payloadLength = length(obj.Body.Payload); end if payloadLength > 0 assert(~isempty(obj.Body.ContentType)); % must be set by caller if ~wasCompleted && any(obj.Method == noBodyMethods) % If not completed, warn for methods that don't expect bodies. This isn't % illegal, but unexpected. warning(message('MATLAB:http:BodyUnexpectedFor', char(obj.Method))); end obj = obj.addOrVerify(ContentTypeField(obj.Body.ContentType), 5); obj = obj.addOrVerify(ContentLengthField(payloadLength), 6); else % If no payload, expect Content-Length to be missing or zero clf = ContentLengthField(0); if ~wasCompleted && any(obj.Method == bodyExpectedMethods) warning(message('MATLAB:http:BodyExpectedFor', char(obj.Method))); % methods that normally expect content should have explicit 0 % as per RFC 7230, section 3.3.2 obj = obj.addOrVerify(clf); else % methods that don't expect body should not have this field, but % if it's there, it should be zero obj.verifyMissingOrEqual(clf); end end % Remove all empty-valued fields. Since the add... methods we called % above won't change a field that's already added, any empty-valued % fields will prevent them from being added. obj.Header(arrayfun(@(x) isempty(x.Value), obj.Header)) = []; obj.Completed = true; end function [uri, proxyURI] = adjustForProxy(obj, uri, proxyURI) % If the user specified a RequestTarget that is absolute, then assume uri is % that of a proxy and return that target as the uri and the uri (minus scheme) % as the proxyURI and ignore the input proxyURI. Otherwise just return the % same uri and proxyURI. This basically undoes what we did when we completed % the request to that proxy, so it's as if the user send to the original % destination in the first place. This allows the user to send a completed % message to a proxy. if ~isempty(obj.RequestLine) target = obj.RequestLine.RequestTarget; if ~isempty(target) && target.Absolute % target absolute; assume input uri is a proxy proxyURI = uri; proxyURI.Scheme = []; uri = target; end end end function obj = addIfEmpty(obj, field, where) % Add header field if one with the same name isn't already in the header. fields = obj.getFields(field.Name); if isempty(fields) if nargin > 2 obj = obj.addFields(where, field); else obj = obj.addFields(field); end else % make sure existing fields that are non-generic have the same value checkForDuplicates(fields(arrayfun(@(f)~isa(f,'matlab.net.http.field.GenericField'), fields))); end end function obj = addOrVerify(obj, field, where) % Add field if one with the same name isn't already in the header, at the % index where. If any found, make sure all that aren't GenericField have the % same value. If field exists but with empty value, ignore. fields = obj.getFields(field.Name); % get all matching fields if isempty(fields) if nargin > 2 % None there, so add it obj = obj.addFields(where, field); else obj = obj.addFields(field); end else fields = getNonGeneric(fields); verifyEqual(fields, field); end end function verifyMissingOrEqual(obj, field) % Verify that there are no non-Generic instances of field, or than any % found are equal to field. fields = obj.getFields(field.Name); verifyEqual(fields, field); end function res = getAllFields(obj, name) % getAllFields(name) returns all HeaderFields matching name % % If any one of them has an empty Value remove it unless it's the only one. res = obj.getFields(name); if length(res) > 1 res(isempty(res.Value)) = []; end end function [response, request, history] = ... sendAndAuthenticate(obj, response, uri, connector, options, ... forProxy, history) % Send a message and authenticate if necessary, using options.Credentials import matlab.net.http.* % In these comments the numbers refer to steps in the credentials % matching algorithm. We exit this code when we got a successful % response or unrecoverable authentication failure response. done = false; authInfos = []; request = obj; if forProxy creds = [options.Credentials options.ProxyCredentials]; else creds = options.Credentials; end while (~done) % Get the most recently used CredentialInfo across all creds that % matches this URI, if any. First time through (authInfo empty), % this only returns a credInfo whose URI completely matches a prefix % of the uri. Later the authInfo needs to match. if forProxy credURI = options.ProxyURI; else credURI = uri; end [cred, credInfo] = creds.getBestCredInfo(credURI, authInfos, forProxy); % step 1 if ~isempty(credInfo) % step 2 % Found a CredentialInfo, so try it proactively. This can % work for Basic or Digest. [response, request, done, history] = ... obj.sendWithCredInfo(uri, connector, options, cred, ... credInfo, forProxy, history); % If done is false, authentication failed but it's worth trying % again with the modified CredentialInfo array in cred, allowing % us to maybe find a different CredentialInfo in the search of % step 1. If done is true, we give up or succeeded. if ~done authInfos = getAuthInfos(response); end else % Found no existing CredentialInfo exactly matching a prefix of % this URI, which means we have no record of previously % authenticating with this URI, so see if any Credentials in % options applies to this URI. step 3 while (~done) if isempty(response) authInfos = []; else authInfos = getAuthInfos(response); if isempty(authInfos) % We got a response, but it didn't contain an expected challenge % (WWW-Authenticate or Proxy-Authenticate) so finish without authenticating return; end end % Get best matching Credentials object. First time through, % authInfos unset, so we'll pick the best one based just on % uri. Next time it's set to any challenges we received so % we have more information such as realm to choose one. cred = creds.getCredentials(credURI, authInfos); % 3.1 if ~isempty(cred) % 3.2 if cred.Scheme == AuthenticationScheme.Basic % 3.2.1 % Basic is the only supported scheme for matching % Credentials, so try to proactively send it [response, request, done, history] = ... obj.sendWithBasic(response, connector, ... options, authInfos, ... uri, creds, cred, forProxy, history); % If done set, we either failed or succeeded. % If false, we loop because another Credentials % object should match the authInfos in the % response. else % A scheme other than Basic is supported by % matching Credentials, so try sending without % credentials if response is empty. [response, request, done, history] = ... obj.sendAfterChallenge(response, connector, ... options, authInfos, uri, cred, forProxy, history); % If done is false, response contains a % challenge so loop and try again with new % authInfos from response. % If done is true, we either succeeded or failed. end else % No credentials match; send unauthenticated request % and return whatever response we get. No retries % after this. This is the normal case when there are % no relevant credentials. 3.3 % Don't send request if we already have response, as this % means we already failed and trying again without % credentials won't help. if isempty(response) [response, request, history] = obj.sendOneRequest(... connector, options, [], forProxy, history); end done = true; end end end end end function [response, request, history] = sendOneRequest(obj, connector, options, ... credInfo, forProxy, history) % Send the request, possibly using credInfo for authentication, if set. If % forProxy set, credInfo applies to the proxy, not the server. import matlab.net.http.* e = []; if forProxy % We may come here first to send a request with proxy % authentication. Once that succeeds, this proxy credInfo remains % set in case we have to send again with server authentication, % redirects, etc. connector.ProxyCredentialInfo = credInfo; end try % This starts the exchange with the server and possibly updates % credInfo. It returns only the header of the response. now = datetime('now'); if ~isempty(connector.ProgressMonitor) connector.ProgressMonitor.Direction = MessageType.Request; end [response, history] = connector.sendRequest(credInfo, history); % If this message has Body.Data, copy that into its copy into history. % connector has only included the Body.Payload. if ~isempty(history(end).Request) % history may have multiple exchanges in case of redirects; each one should % have the Data if ~isempty(obj.Body) && ~isempty(obj.Body.Data) for i = 1 : length(history) if isempty(history(i).Request.Body) % If there was no body in the history, it's because connector wasn't asked to % save the payload. In that case, we have to create one. This will mean that % Completed is not set in the record. history(i).Request.Body = MessageBody; end % Now set Body.Data using DataInt so that it doesn't clear Payload. % Since this resets Request.Completed, save and restore it. wasCompleted = history(i).Request.Completed; history(i).Request.Body.DataInt = obj.Body.DataInt; history(i).Request.Completed = wasCompleted; end end end catch e % save exception for below end if nargout > 1 || log || ~isempty(e) % If user wanted us to return the request, or there was an exception, % populate returned request with all fields that the connector % actually put in it, plus any Body.Data. try request = connector.getRequest(); if ~isempty(obj.Body) % There's a body to the original request. The connector didn't % have the original Data, so put that into the message of there % is any. if ~isempty(request.Body) completed = request.Completed; % save because following line clears request.Body.DataInt = obj.Body.DataInt; request.Completed = completed; else % The connector didn't create a Body because there was a payload and we didn't % tell it to save the payload, so just copy the caller's Body. In this case % copy the Completed state from this message, which basically indicates % whether Body.Payload was set or not (if there was Data). request.Body = obj.Body; request.Completed = obj.Completed; end else % On an empty Body, we can mark Completed request.Completed = true; end catch e % don't expect exception in block above; must be internal error rethrow(e); end if ~isempty(e) % This occurs if we couldn't open the connection, send the whole request, or % get a whole response from the server. Need to append a history record % because sendRequest didn't have a chance to do so. % TBD The logic to create the last history record when an exception occurs % before receiving the whole response header is already in HTTPConnector, but % there it's used only in the case of an exception during a redirect. That % process, and creating an HTTPException, should be in HTTPConnector in all % cases. % % Another option is to not have HTTPConnector handle redirects at all, but % have that come back to here, similarly to the way we come back here to % handle authentication challenges. Then all the exception handling could be % here. if isa(e, 'matlab.net.http.HTTPException') % If HTTPConnector threw HTTPException, it already has the whole exception, so % rethrow. throwAsCaller(e); end history(end+1) = LogRecord; history(end).URI = connector.URI; history(end).RequestTime = now; history(end).Request = request; if (connector.Sent) % If request was successfully sent, the exception likely % occurred during or after receipt of response (or there was % a timeout), so add any response to the history, if there % was one, but mark it not completed history(end).Response = connector.getResponse(); if ~isempty(history(end).Response) history(end).Response.Completed = false; end end history(end).Disposition = Disposition.TransmissionError; history(end).Exception = e; throwException(connector.URI, request, history, e); end end % If we get here, the server sent us a response header; time to read any % data try import matlab.net.http.internal.readContentFromWebService history(end).ResponseTime(1) = datetime('now'); savePayload = nargin > 2 && (options.Debug || options.SavePayload); % true if data is compressed but we shouldn't decode it (e.g., it's an unknown % encoding or user specified DecodeResponse=false) raw = ~connector.Decoded; convert = options.ConvertResponse; if savePayload || raw % if debugging or logging, get raw payload as well % if raw set, data is empty [data, payloadLength, payload, charset] = ... readContentFromWebService(connector, options.Debug, ... convert, raw); else % if not debugging or logging, history gets just converted data [data, payloadLength, ~, charset] = ... readContentFromWebService(connector, options.Debug, ... convert); end % Success: create a MessageBody that contains the data and optionally % the payload. body = MessageBody(data); if convert body.ContentType = history(end).Response.getBodyContentType(); else % charset is set when full conversion of payload to data wasn't done based on % the message Content-Type, but only the conversion to Unicode. This would % happen if ConvertResponse is false (~convert), the Content-Type is % character-based, and any decoding was successfully applied (~raw). In this % case set only that charset in the body's ContentType. if ~isempty(charset) m = MediaType; body.ContentType = m.setParameter('charset', charset); end end history(end).Response.Body = body; history(end).ResponseTime(2) = datetime('now'); if savePayload || raw % Use PayloadInt to save payload without clearing Data history(end).Response.Body.PayloadInt = payload; body.PayloadInt = payload; if raw % If raw, there must have been a Content-Encoding field and % we didn't decode it, so save the value of the field. codingFields = history(end).Response.getFields('Content-Encoding'); if ~isempty(codingFields) body.ContentCoding = codingFields.parse(); history(end).Response.Body.ContentCoding = body.ContentCoding; end end end history(end).Response.Body.PayloadLength = payloadLength; history(end).Response.Completed = ~raw; history(end).Disposition = Disposition.Done; catch e % Header received OK, but couldn't read or process payload if nargin < 2 % need to populate request because we didn't do it above in the % no exception, no log case request = connector.getRequest(); end if isa(e, 'matlab.net.http.internal.ExceptionWithPayload') % If an exception occurs after receipt of payload (i.e., couldn't % convert the data based on ContentType), create a Body and save % payload in history. If there is Data, this would be chars converted based % on the charset in Content-Type field. body = MessageBody(e.Data); body.PayloadInt = e.Payload; if ~isempty(e.Data) % If there is Data, the content type was character-based and the exception % occurred converting the payload to Unicode. In this case create a MediaType % object containing only the charset. m = MediaType; body.ContentType = m.setParameter('charset',e.Charset); end history(end).Response.Body = body; history(end).Disposition = Disposition.ConversionError; % get original cause of the processing error history(end).Exception = e.cause{1}; else history(end).Disposition = Disposition.TransmissionError; end history(end).Response.Completed = false; history(end).ResponseTime(2) = datetime('now'); throwException(connector.URI, request, history, e); end response.Body = body; if options.SavePayload response.Body.PayloadInt = payload; end response.Body.PayloadLength = payloadLength; % At this point, since we didn't error out, Body.Data should have always have % the converted data if raw wasn't set and there was any payload, or be empty % if raw was set or there was no payload. In the case where there was no % payload, or if there was a payload and Data was set, mark it completed only % if Body.Payload was also set. % % We only want to mark a ResponseMessage completed if it had no payload or if % it had a payload and both Payload and Data are set. In the latter case % Body.ContentType was set to the type we used to convert Payload to Data. In % most cases when we receive a payload that was successfully convered to Data, % the payload isn't being saved, so Body.Payload will be empty and we don't mark % it Completed. Also it should never be marked Completed if raw is set unless % there was no payload. In the test below, in the ~raw case, we can safely % assume that an empty Body.Data means there was no payload, or a nonempty % Payload means we have body Payload and Data. response.Completed = (raw && isempty(response.Body.Payload)) || ... (~raw && (isempty(response.Body) || isempty(response.Body.Data) || ... ~isempty(response.Body.Payload))); end function [response, request, done, history] = ... sendWithCredInfo(obj, uri, connector, options, cred, ... credInfo, forProxy, history) % Send message, attempting to use a candidate CredentialInfo that we % previously used successfully. If unsuccessful, maybe try to get new % credInfo based on information in Credentials cred and use that instead % (which may include calling GetCredentialsFcn). If successful add the new % credInfo or update the existing one so it can be used for the next message % (needed for Digest). If unsuccessful may remove credInfo from cred if % determined to be no good. % % done if true, we succeeded or gave up. Caller should return response. % if false, we modified the cred.CredentialInfo vector, so caller % should try again to find another CredentialInfo. % % The response contains either the response to the request or, in the % unsuccessful case, the last challenge. done = true; BasicScheme = matlab.net.http.AuthenticationScheme.Basic; [response, request, history] = obj.sendOneRequest(connector, options, ... credInfo, forProxy, history); % 2.1 if authenticationSuccess(response, forProxy) % 2.2 credInfo.LastUsed = datetime('now'); else % authentication failed; we got a challenge in response 2.3 authInfos = getAuthInfos(response); if isBasicChallenge(authInfos, cred) % 2.3.1 % Basic is the only scheme offered by the challenge that this % cred object supports. Since the particular credInfo failed, % either the login information changed or this URI is for a % different path or that needs a different credInfo. assert(credInfo.AuthInfo.Scheme == BasicScheme && ... isscalar(credInfo.URIs)); % Basic credInfo should have only one URI if pathCompare(uri,credInfo.URIs) % 2.3.1.1 % Target URI has the same path as credInfo, but the credInfo % didn't work, so remove it and tell caller to try another one. cred.delete(credInfo); % 2.3.1.1.1 done = false; else % Path is different; maybe we need new Basic credentials for % this path 2.3.1.2 if ~isempty(cred.GetCredentialsFcn) % 2.3.1.2.1 % If a GetCredentialsFcn, try to get a new Basic % CredentialInfo. [response, request, history, done] = ... obj.sendWithNewCredinfo(cred, uri, response, ... authinfos, forProxy, connector, options, ... history, true); end % Without a GetCredentialsFcn, there's nothing else to try, % so give up; response contains last challenge 2.3.1.2.2.1 end else % Challenge and cred allows schemes other than Basic. 2.3.2 % Delete the credInfo that got us here let caller try again. % Exception: if this is a proxy credInfo with no AuthInfo, we % haven't received a challenge yet, so need to give this another % try after the challenge. if ~credInfo.ForProxy || ~isempty(credInfo.AuthInfo) cred.delete(credInfo); % 2.3.2.1 end done = false; end end end function [response, request, done, history] = ... sendAfterChallenge(obj, response, connector, options, authInfos, ... uri, cred, forProxy, history) % If response is empty, send the message without authentication and return % response and any challenge in authInfos. If response is set and contains % a challenge (authInfos), use cred as the candidate Credentials object that % satisfies this authInfos, where authInfos and cred allows for schemes other % than Basic. 3.2.2 % % If there is no response yet from a previous try (response is []), we'll try % without authentication. If we get a challenge, authInfos contains the % authentication challenges to which we need to respond and done = false. % Caller should use authInfos to rescan get the Credentials array to possibly % find a better Credentials object maching the information in authInfos. % % If we already have a response (which contains one or more authInfos), done % is false, caller should try again using authInfos, with possibly new cred, % or give up if there aren't any. done = true; % if set on return, we're done (success or fail) if isempty(response) % 3.2.2.1 % no challenge received yet, try without authentication [response, request, history] = obj.sendOneRequest(connector, ... options, [], forProxy, history); % 3.2.2.1.1 if authenticationSuccess(response, forProxy) % no authentication was required; done % This is the case we'll hit every time when we have a matching % Credentials object (which could include any Credentials with an % empty URI and Scheme) when the server doesn't require % authentication. % 3.2.2.1.2.1 else % authentication needed 3.2.2.1.3.1 done = false; end else % We already have a challenge, so send request with new CredentialInfo % created from a challenge. This returns done=true on success or give % up; done=false to try another cred with these authInfos, if possible. [response, request, history, done] = obj.sendWithNewCredInfo(cred, ... uri, response, authInfos, forProxy, connector, ... options, history, false); end end function [response, request, done, history] = ... sendWithBasic(obj, response, connector, options, authInfos, uri, ... creds, cred, forProxy, history) % This is called to send a message after determining that there is no % existing CredentialInfo whose URI exactly matches a prefix of the request % URI, but we found a Credentials object (cred) that is a candidate match, % and the only scheme supported by that Credentials object is Basic. This % allows us to proactively authenticate with username and password without % first receiving a challenge. On return, if done is set, caller should % finish. If done is false, we updated creds and caller should try again. import matlab.net.http.* done = true; % First see if any existing CredentialInfo that *shares* the same Path % prefix as this URI works. 3.2.1.1 This isn't the same as the *full* % prefix match the caller has made. This is to avoid calling the % GetCredentialsFcn if we have an existing CredentialInfo that might work. % For example, for a URI of /foo/bar/bat, a CredentialInfo of /foo/bar/baz % would not have been a full prefix match, but it does share a common % prefix of /foo/bar. In fact, any CredentialInfo with the properties up % to (but not necessarily) including Path is considered to match. % This should return all matching ones, sorted by longest match first credInfos = cred.getCommonPrefixCredInfos(uri); for i = 1 : length(credInfos) credInfo = credInfos(i); % Try each one that matches until success [response, request, history] = obj.sendOneRequest(connector, ... options, credInfo, forProxy, history); % 3.2.1.2.1 if authenticationSuccess(response, forProxy) % It works; trim the existing credInfo to contain just the common % prefix credInfo.chopCommonPrefix(uriURL); % 3.2.1.2.2.1 credInfo.LastUsed = datetime('now'); return; end end % None worked, or there was no common prefix % 3.2.1.3 if ~isempty(response) % If we got a challenge from a previous attempt to authenticate, use % it to get possibly new Credentials. In this case the challenge % might allow for a stronger scheme than Basic if isempty(authInfos) % bad challenge if it contains no authInfo return; else if all([authInfos.Scheme] == AuthenticationScheme.Basic) % All challenges are Basic; try getting new credentials using % realm from first authInfos (really, there should be only one % such challenge, legally) realm = authInfos(1).getParameter('realm'); else % Challenges allow other than Basic. If we have any matching % credentials that supports that scheme, loop and try again. % If we don't, and if Basic still allowed, keep working on % Basic % Get best matching Credentials across all challenges cred = creds.getCredentials(uri, request, ... response, authInfos); if cred.Scheme ~= AuthenticationScheme.Basic % Best matching Credentials supports another scheme, so % return to try again. This time, since we just % determined that we do have a Credentials match to a % non-Basic challenge, our caller shouldn't be invoking us % a 2nd time. done = false; return; end % Our match is only for the Basic scheme, so see if challenge % allows Basic authInfo = getAuthInfos(response, AuthenticationScheme.Basic); if isempty(authInfo) % Challenge doesn't allow Basic, but Basic is the only one % we have Credentials for that matches this URI, so we % can't authenticate. return; else % Challenge allows Basic. Get realm from the challenge, % if any realm = authInfo.getParameter('realm'); end end end else realm = []; end % Create a new Basic CredentialInfo and try it authInfo = AuthInfo(AuthenticationScheme.Basic, 'realm', realm); [response, request, history, done] = obj.sendWithNewCredInfo(... cred, uri, response, authInfo, forProxy, ... connector, options, history, false); end function [response, request, history, done] = sendWithNewCredInfo(obj, ... cred, uri, response, authInfos, forProxy, ... connector, options, history, forBasic) % Get a new CredentialInfo from Credentials object cred, based on the most % appropriate challenge in authInfos, and send the request with that % CredentialInfo. May invoke the GetCredentialsFcn in cred repeatedly, if % one is set. Sets done if we succeeded or should give up because we failed % authentication and there was no GetCredentialsFcn or the GetCredentialsFcn % told us to give up. Sets done to false if cred is not appropriate for any % authInfos, indicating that perhaps another cred should be tried. % % If successful, conditionally add the new CredentialInfo to the Credentials % object, replace an existing one, or edit an existing one to contain just % the common prefix. % % If forBasic is set, try only Basic authentication regardless of the % challenges. If successful, add the new CredentialInfo unconditionally. credInfo = []; done = false; % copy obj to request becasue the loop below may keep updating request and % we want to return the latest request that was sent request = obj; while true credInfo = cred.createCredInfo(uri, request, response, authInfos, ... forProxy, credInfo); % 2.3.1.2.1.1, 3.2.1.3.1 if isempty(credInfo) % This cred is not appropriate for any authInfos. Tell caller % to try again with possibly different Credentials. break; elseif isnumeric(credInfo) && credInfo == 0 % give up, can't get new credentials done = true; break; end [response, request, history] = ... obj.sendOneRequest(connector, options, ... credInfo, forProxy, history); % 2.3.1.2.1.2, 3.2.2.2.1, 3.2.1.3.2 if authenticationSuccess(response, forProxy) % 2.3.1.2.1.3, 3.2.2.2.2 % if new CredentialInfo works, add it, even if it shares a common % prefix with an existing one, unless forBasic is set cred.addCredInfo(credInfo, ~forBasic); % 3.2.2.2.2.1, 3.2.1.3.3.1 done = true; break; % 3.2.2.2.2.1, 3.2.1.3.3.2 elseif isempty(cred.GetCredentialsFcn) % 3.2.1.3.4.1 % fail; give up if no GetCredentialsFcn done = true; break; % 3.2.2.2.3.2.1, 3.2.1.3.4.2.1 end if forBasic authInfos = getAuthInfos(response, matlab.net.http.AuthenticationScheme.Basic); else authInfos = getAuthInfos(response); end end end end methods (Static, Access=protected) function type = getStartLineType() type = 'matlab.net.http.RequestLine'; end function checkBody(~) %{ % Since MessageBody accepts anything, then so do we % checkBody(body) Throw error if body is not valid for RequestMessage % Caller has not verified anything about body. % % TBD doesn't check Body.Data. If uint8 or char, should check that it's a % vector. Check other types based on some TBD list of allowed types that % we know how to process, like struct for JSON and JSON object. validateattributes(body, {'matlab.net.http.MessageBody', ... 'matlab.net.http.ContentProvider'}, {'scalar'}, mfilename); %} end function badField = getInvalidFields(fields) % getInvalidFields(fields) Return the first field not valid for RequestHeader. % If the name is OK but the value is invalid, return the whole field. % Otherwise return the field with the value empty. % % Currently only checks field names. Could be expanded to check values, but % would only need to check for valid values in fields which are allowed in % both requests and responses, for which we haven't defined subclasses, % where the values in requests are more constrained than those in % responses. This is because the subclass already verifies valid values. % An example of the latter is the Cache-Control field, which allows % different directives in a request than it does in a response. df = matlab.net.http.RequestMessage.DisallowedFields; badFields = ... arrayfun(@(x) ~isa(x,'matlab.net.http.field.Generic') && ... any(strcmpi(x.Name, df)), fields); idx = find(badFields, 1); if isempty(idx) badField = []; else badField = fields(idx); badField.Value = []; % clear because only the name is invalid end end function tf = shouldSetBodyContentType() % Return true to indicate that we want to set the body's ContentType whenever % the ContentTypeField in this message is set. tf = true; end end methods (Access=protected) function group = getPropertyGroups(obj) % Provide a custom display that removes StartLine (inherited from Message), % as it's redundant with RequestLine. Also displays RequestLine as string. group = getPropertyGroups@matlab.mixin.CustomDisplay(obj); if isscalar(obj) group.PropertyList = rmfield(group.PropertyList,'StartLine'); group.PropertyList.RequestLine = char(group.PropertyList.RequestLine); end end function obj = checkHeader(obj, header) % Given a Header, throw error if it contains any fields invalid for a % RequestMessage. Caller must verify it's a matlab.net.http.Header. badField = matlab.net.http.RequestMessage.getInvalidFields(header); if ~isempty(badField) if isempty(badField.Value) error(message('MATLAB:http:BadFieldNameInHeader', ... badField.Name, class(obj))); else error(message('MATLAB:http:BadFieldValueInHeader', ... badField.Name, badField.Value, class(obj))); end end end end end function fields = getNonGeneric(fields) % Return fields in array of HeaderField that are not GenericField whose values % are not [] fields = fields(arrayfun(@(f) ... ~(metaclass(f) <= ?matlab.net.http.field.GenericField) && ~isempty(f.Value), fields)); end function verifyEqual(fields, field) % Throw error if any of the nonempty non-GenericField fields is not equal to % field. Compares using isequal on the whole field, which uses convert() if % implemented. if ~isempty(fields) fields = getNonGeneric(fields); if ~isempty(fields) bad = find(arrayfun(@(f) ~isequal(f,field), fields), 1); if ~isempty(bad) error(message('MATLAB:http:InconsistentHeaderValue', ... char(field.Name), char(fields(bad).Value), ... char(field.Value))); end end end end function authInfo = getAuthInfos(response, scheme) % getAuthInfos returns authentication challenges from the response. % If scheme not specified, return array of AuthInfos from % AuthenticateField.convert() for all AuthenticationFields in response. If scheme % (an AuthenticationScheme) specified, return only a single authInfo structure for % the first match with that scheme. Which field we look at depends on the response % message: % % response.StatusCode AuthenticateField % --------------------------- ------------------ % Unauthorized WWW-Authenticate % ProxyAuthenticationRequired Proxy-Authenticate % % Returns [] if there are no AuthenticateFields or no challenges match scheme. import matlab.net.http.* if response.StatusCode == StatusCode.Unauthorized fieldName = 'WWW-Authenticate'; else assert(response.StatusCode == StatusCode.ProxyAuthenticationRequired); fieldName = 'Proxy-Authenticate'; end fields = response.getFields(fieldName); % array of fields matching fieldName if isempty(fields) authInfo = []; else % concatenate into a single array if multiple fields authInfo = arrayfun(@convert, fields, 'UniformOutput', false); authInfo = [authInfo{:}]; % uncellify if nargin > 1 % if scheme specified, return only the first one matching scheme for i = 1 : length(authInfo) % note authInfo.Scheme could be a string or '' if scheme == authInfo(i).Scheme authInfo = authInfo(i); return; end end authInfo = []; end end end function tf = authenticationSuccess(response, forProxy) % Test whether the response implies authentication success import matlab.net.http.StatusCode tf = (~forProxy && response.StatusCode ~= StatusCode.Unauthorized) || ... (forProxy && response.StatusCode ~= StatusCode.ProxyAuthenticationRequired); end function tf = isBasicChallenge(authInfos, cred) % Test whether the authInfos and cred intersect only in Basic BasicScheme = matlab.net.http.AuthenticationScheme.Basic; tf = (isempty(cred.Scheme) && all([authInfo.Scheme] == BasicScheme)) || ... all(cred.Scheme(ismember(cred.Scheme, [authInfos.Scheme])) == BasicScheme); end function [proxy, username, password] = getProxySettings(uri) % Get proxy settings from MATLAB preferences panel or the system % Returns [] if there is no proxy setting username = []; password = []; proxy = []; if usejava('jvm') % Get the proxy information using the MATLAB proxy API. % Ensure the Java proxy settings are set. com.mathworks.mlwidgets.html.HTMLPrefs.setProxySettings % Obtain the proxy information. if isempty(uri.Port) port = -1; else port = uri.Port; end url = java.net.URL(char(uri.Scheme), char(uri.Host), port, char(uri.EncodedPath)); % This function goes to MATLAB's preference panel or (if not set and on % Windows) the system preferences. javaProxy = com.mathworks.webproxy.WebproxyFactory.findProxyForURL(url); if ~isempty(javaProxy) address = javaProxy.address; if isa(address,'java.net.InetSocketAddress') && ... javaProxy.type == javaMethod('valueOf','java.net.Proxy$Type','HTTP') proxy = matlab.net.URI('http://'); proxy.Host = char(address.getHostName()); proxy.Port = address.getPort(); if nargout > 1 % If proxy information came from MATLAB settings, also get the % username and password. If not, any username/password required % to get to the proxy will have to be set by the caller in % HTTPOptions.Credentials. mwt = com.mathworks.net.transport.MWTransportClientPropertiesFactory.create(); if ~isempty(mwt.getProxyHost()) username = char(mwt.getProxyUser()); password = char(mwt.getProxyPassword()); end end end end else % The Java JVM is not running. The MATLAB proxy information is obtained % from the MATLAB preferences or the system using Java. end end function options = updateProxyCredentials(options, proxyURI, username, password) % Check for or create a Credentials object in options that contains the username and % password for the proxy. This is used in the case where the proxy username and % password came from preferences (or elsewhere, not from the Credentials array % created by the user). Normally, if the user did nothing with Credentials, we'll % find the special ProxyCredentials that matches all proxyURIs. % If there is already a Credentials for this proxy in place, our use of it to % authenticate to this proxy may add or update a CredentialInfo within it. creds = [options.Credentials options.ProxyCredentials]; % never empty % First see if there's a matching CredentialInfo that we already created for this % proxy. If there is one, assure that its username and password are correct. % This means we already authenticated to the proxy before, possibly allowing us % to authenticate without first getting a challenge. [~, credInfo] = creds.getBestCredInfo(proxyURI, [], true); if ~isempty(credInfo) credInfo.updateCreds(username, password); else % No CredentialInfo found, so see if there's a Credentials that matches this % proxyURI. % The following is never empty because ProxyCredentials matches all URIs cred = creds.getCredentials(proxyURI, []); if isscalar(cred.Scope) && cred.Scope == proxyURI % We got a match, and it has just one Scope that matches this proxyURI % exactly. Since we know it can apply only to this proxy, force its % username and password to be the desired one. cred.Username = username; cred.Password = password; elseif ~isequal(cred.Username,username) || ~isequal(cred.Password,password) % Its scope is empty or has more than one URI, and the username and % password don't both match. In this case create a new % CredentialInfo under this Credential that contains the needed % information. cred.addProxyCredInfo(proxyURI, username, password); end end end function throwException(uri, request, history, e) % Throw an HTTPException with information from e. id = string(e.identifier); if ~id.startsWith('MATLAB:webservices') && ~id.startsWith('MATLAB:http') && ... ~string(class(e)).startsWith('matlab.net') % if we got some unexpected MATLAB exception rather than one related to % webservices, just rethrow it directly newe = MException('MATLAB:http:UncaughtException', '%s', ... message('MATLAB:http:UncaughtException').getString()); throw(newe.addCause(e)); else if isa(e, 'matlab.net.http.internal.ExceptionWithPayload') % If we got an internal exception that wraps some other exception, unwrap % the real exception (stored as a cause) from it, because we don't want % the user to see the internal exception. If the internal one has more % than one cause, we'll only capture the first one, but we do capture any % causes of the real exception. e = e.cause{1}; end % make it look like our caller threw this throwAsCaller(matlab.net.http.HTTPException(uri, request, history, e)) end end function tf = pathCompare(uri,uris) % Return true if any path in uris exactly matches path in uri tf = any(arrayfun(@(x) isequal(uri.Path,x.Path), uris)); end function checkForDuplicates(fields) % Given vector of fields with the same Name, throw error if they don't all have the % same value. if ~isempty(fields) && length(fields) > 1 && ... any(~isequal(fields.Value,fields(1).Value)) error(message('MATLAB:http:DuplicateHeaderFields', char(fields(1).Name))); end end function [uri, origURI] = createURIFromInput(uri) % Validate the uri as a URI or a string and fill in default properties. If % uri is a string, this assumes the first token is a host. Then it fills in % the Scheme if necessary. OrigURI is the completed URI but without a Scheme % added (unless it already contained one). Returned uri has the Scheme. import matlab.net.internal.getString if isa(uri, 'matlab.net.URI') validateattributes(uri, {'matlab.net.URI','string'}, ... {'scalar'}, mfilename, 'URI'); elseif ischar(uri) || isstring(uri) str = getString(uri, mfilename, 'uri'); uri = matlab.net.URI.assumeHost(str); if isempty(uri.Host) error(message('MATLAB:http:URIMustNameHost', char(str))); end else validateattributes(uri, {'matlab.net.URI','string','char'}, ... {'scalar'}, mfilename, 'URI'); end origURI = uri; if isempty(uri.Scheme) % allow Scheme to be missing uri.Scheme = 'http'; else if uri.Scheme ~= 'http' && uri.Scheme ~= 'https' error(message('MATLAB:http:UnsupportedScheme', char(uri.Scheme))); end end end