mirror of
https://github.com/vdemydiuk/mtapi.git
synced 2026-07-28 02:57:56 +00:00
76a6216cf7
It uses its own API which I packed between MTApi5 and MatLab. Possible that this is useless, but errors from the .NET assembly are more tradable.
372 lines
13 KiB
Matlab
372 lines
13 KiB
Matlab
function [output,extras] = urlread2(urlChar,method,body,headersIn,varargin)
|
|
%urlread2 Makes HTTP requests and processes response
|
|
%
|
|
% [output,extras] = urlread2(urlChar, *method, *body, *headersIn, varargin)
|
|
%
|
|
% * indicates optional inputs that must be entered in place
|
|
%
|
|
% UNDOCUMENTED MATLAB VERSION
|
|
%
|
|
% EXAMPLE CALLING FORMS
|
|
% ... = urlread2(urlChar)
|
|
% ... = urlread2(urlChar,'GET','',[],prop1,value1,prop2,value2,etc)
|
|
% ... = urlread2(urlChar,'POST',body,headers)
|
|
%
|
|
% FEATURES
|
|
% =======================================================================
|
|
% 1) Allows specification of any HTTP method
|
|
% 2) Allows specification of any header. Very little is hard-coded
|
|
% in for header handling.
|
|
% 3) Returns response status and headers
|
|
% 4) Should handle unicode properly ...
|
|
%
|
|
% OUTPUTS
|
|
% =======================================================================
|
|
% output : body of the response, either text or binary depending upon
|
|
% CAST_OUTPUT property
|
|
% extras : (structure)
|
|
% .allHeaders - stucture, fields have cellstr values, HTTP headers may
|
|
% may be repeated but will have a single field entry, with each
|
|
% repeat's value another being another entry in the cellstr, for
|
|
% example:
|
|
% .Set_Cookie = {'first_value' 'second_value'}
|
|
% .firstHeaders - (structure), variable fields, contains the first
|
|
% string entry for each field in allHeaders, this
|
|
% structure can be used to avoid dereferencing a cell
|
|
% for fields you expect not to be repeated ...
|
|
% EXAMPLE:
|
|
% .Response : 'HTTP/1.1 200 OK'}
|
|
% .Server : 'nginx'
|
|
% .Date : 'Tue, 29 Nov 2011 02:23:16 GMT'
|
|
% .Content_Type : 'text/html; charset=UTF-8'
|
|
% .Content_Length : '109155'
|
|
% .Connection : 'keep-alive'
|
|
% .Vary : 'Accept-Encoding, User-Agent'
|
|
% .Cache_Control : 'max-age=60, private'
|
|
% .Set_Cookie : 'first_value'
|
|
% .status - (structure)
|
|
% .value : numeric value of status, ex. 200
|
|
% .msg : message that goes along with status, ex. 'OK'
|
|
% .url - eventual url that led to output, this can change from
|
|
% the input with redirects, see FOLLOW_REDIRECTS
|
|
% .isGood - (logical) I believe this is an indicator of the presence of 400
|
|
% or 500 status codes (see status.value) but more
|
|
% testing is needed. In other words, true if status.value < 400.
|
|
% In code, set true if the response was obtainable without
|
|
% resorting to checking the error stream.
|
|
%
|
|
% INPUTS
|
|
% =======================================================================
|
|
% urlChar : The full url, must include scheme (http, https)
|
|
% method : examples: 'GET' 'POST' etc
|
|
% body : (vector)(char, uint8 or int8) body to write, generally used
|
|
% with POST or PUT, use of uint8 or int8 ensures that the
|
|
% body input is not manipulated before sending, char is sent
|
|
% via unicode2native function with ENCODING input (see below)
|
|
% headersIn : (structure array), use empty [] or '' if no headers are needed
|
|
% but varargin property/value pairs are, multiple headers
|
|
% may be passed in as a structure array
|
|
% .name - (string), name of the header, a name property is used
|
|
% instead of a field because the name must match a valid
|
|
% header
|
|
% .value - (string), value to use
|
|
%
|
|
% OPTIONAL INPUTS (varargin, property/value pairs)
|
|
% =======================================================================
|
|
% CAST_OUTPUT : (default true) output is uint8, useful if the body
|
|
% of the response is not text
|
|
% ENCODING : (default ''), ENCODING input to function unicode2native
|
|
% FOLLOW_REDIRECTS : (default true), if false 3xx status codes will
|
|
% be returned and need to be handled by the user,
|
|
% note this does not handle javascript or meta tag
|
|
% redirects, just server based ones
|
|
% READ_TIMEOUT : (default 0), 0 means no timeout, value is in
|
|
% milliseconds
|
|
%
|
|
% EXAMPLES
|
|
% =======================================================================
|
|
% GET:
|
|
% --------------------------------------------
|
|
% url = 'http://www.mathworks.com/matlabcentral/fileexchange/';
|
|
% query = 'urlread2';
|
|
% params = {'term' query};
|
|
% queryString = http_paramsToString(params,1);
|
|
% url = [url '?' queryString];
|
|
% [output,extras] = urlread2(url);
|
|
%
|
|
% POST:
|
|
% --------------------------------------------
|
|
% url = 'http://posttestserver.com/post.php';
|
|
% params = {'testChars' char([2500 30000]) 'new code' '?'};
|
|
% [paramString,header] = http_paramsToString(params,1);
|
|
% [output,extras] = urlread2(url,'POST',paramString,header);
|
|
%
|
|
% From behind a firewall, use the Preferences to set your proxy server.
|
|
%
|
|
% See Also:
|
|
% http_paramsToString
|
|
% unicode2native
|
|
% native2unicode
|
|
%
|
|
% Subfunctions:
|
|
% fixHeaderCasing - small subfunction to fix case errors encountered in real
|
|
% world, requires updating when casing doesn't match expected form, like
|
|
% if someone sent the header content-Encoding instead of
|
|
% Content-Encoding
|
|
%
|
|
% Based on original urlread code by Matthew J. Simoneau
|
|
%
|
|
% VERSION = 1.1
|
|
|
|
in.CAST_OUTPUT = true;
|
|
in.FOLLOW_REDIRECTS = true;
|
|
in.READ_TIMEOUT = 0;
|
|
in.ENCODING = '';
|
|
|
|
%Input handling
|
|
%---------------------------------------
|
|
if ~isempty(varargin)
|
|
for i = 1:2:numel(varargin)
|
|
prop = upper(varargin{i});
|
|
value = varargin{i+1};
|
|
if isfield(in,prop)
|
|
in.(prop) = value;
|
|
else
|
|
error('Unrecognized input to function: %s',prop)
|
|
end
|
|
end
|
|
end
|
|
|
|
if ~exist('method','var') || isempty(method), method = 'GET'; end
|
|
if ~exist('body','var'), body = ''; end
|
|
if ~exist('headersIn','var'), headersIn = []; end
|
|
|
|
assert(usejava('jvm'),'Function requires Java')
|
|
|
|
import com.mathworks.mlwidgets.io.InterruptibleStreamCopier;
|
|
com.mathworks.mlwidgets.html.HTMLPrefs.setProxySettings %Proxy settings need to be set
|
|
|
|
%Create a urlConnection.
|
|
%-----------------------------------
|
|
urlConnection = getURLConnection(urlChar);
|
|
%For HTTP uses sun.net.www.protocol.http.HttpURLConnection
|
|
%Might use ice.net.HttpURLConnection but this has more overhead
|
|
|
|
%SETTING PROPERTIES
|
|
%-------------------------------------------------------
|
|
urlConnection.setRequestMethod(upper(method));
|
|
urlConnection.setFollowRedirects(in.FOLLOW_REDIRECTS);
|
|
urlConnection.setReadTimeout(in.READ_TIMEOUT);
|
|
|
|
for iHeader = 1:length(headersIn)
|
|
curHeader = headersIn(iHeader);
|
|
urlConnection.setRequestProperty(curHeader.name,curHeader.value);
|
|
end
|
|
|
|
if ~isempty(body)
|
|
%Ensure vector?
|
|
if size(body,1) > 1
|
|
if size(body,2) > 1
|
|
error('Input parameter to function: body, must be a vector')
|
|
else
|
|
body = body';
|
|
end
|
|
end
|
|
|
|
if ischar(body)
|
|
%NOTE: '' defaults to Matlab's default encoding scheme
|
|
body = unicode2native(body,in.ENCODING);
|
|
elseif ~(strcmp(class(body),'uint8') || strcmp(class(body),'int8'))
|
|
error('Function input: body, should be of class char, uint8, or int8, detected: %s',class(body))
|
|
end
|
|
|
|
urlConnection.setRequestProperty('Content-Length',int2str(length(body)));
|
|
urlConnection.setDoOutput(true);
|
|
outputStream = urlConnection.getOutputStream;
|
|
outputStream.write(body);
|
|
outputStream.close;
|
|
else
|
|
urlConnection.setRequestProperty('Content-Length','0');
|
|
end
|
|
|
|
%==========================================================================
|
|
% Read the data from the connection.
|
|
%==========================================================================
|
|
%This should be done first because it tells us if things are ok or not
|
|
%NOTE: If there is an error, functions below using urlConnection, notably
|
|
%getResponseCode, will fail as well
|
|
try
|
|
inputStream = urlConnection.getInputStream;
|
|
isGood = true;
|
|
catch ME
|
|
isGood = false;
|
|
%NOTE: HTTP error codes will throw an error here, we'll allow those for now
|
|
%We might also get another error in which case the inputStream will be
|
|
%undefined, those we will throw here
|
|
inputStream = urlConnection.getErrorStream;
|
|
|
|
if isempty(inputStream)
|
|
msg = ME.message;
|
|
I = strfind(msg,char([13 10 9])); %see example by setting timeout to 1
|
|
%Should remove the barf of the stack, at ... at ... at ... etc
|
|
%Likely that this could be improved ... (generate link with full msg)
|
|
if ~isempty(I)
|
|
msg = msg(1:I(1)-1);
|
|
end
|
|
fprintf(2,'Response stream is undefined\n below is a Java Error dump (truncated):\n');
|
|
error(msg)
|
|
end
|
|
end
|
|
|
|
%POPULATING HEADERS
|
|
%--------------------------------------------------------------------------
|
|
allHeaders = struct;
|
|
allHeaders.Response = {char(urlConnection.getHeaderField(0))};
|
|
done = false;
|
|
headerIndex = 0;
|
|
|
|
while ~done
|
|
headerIndex = headerIndex + 1;
|
|
headerValue = char(urlConnection.getHeaderField(headerIndex));
|
|
if ~isempty(headerValue)
|
|
headerName = char(urlConnection.getHeaderFieldKey(headerIndex));
|
|
headerName = fixHeaderCasing(headerName); %NOT YET FINISHED
|
|
|
|
%Important, for name safety all hyphens are replace with underscores
|
|
headerName(headerName == '-') = '_';
|
|
if isfield(allHeaders,headerName)
|
|
allHeaders.(headerName) = [allHeaders.(headerName) headerValue];
|
|
else
|
|
allHeaders.(headerName) = {headerValue};
|
|
end
|
|
else
|
|
done = true;
|
|
end
|
|
end
|
|
|
|
firstHeaders = struct;
|
|
fn = fieldnames(allHeaders);
|
|
for iHeader = 1:length(fn)
|
|
curField = fn{iHeader};
|
|
firstHeaders.(curField) = allHeaders.(curField){1};
|
|
end
|
|
|
|
status = struct(...
|
|
'value', urlConnection.getResponseCode(),...
|
|
'msg', char(urlConnection.getResponseMessage));
|
|
|
|
%PROCESSING OF OUTPUT
|
|
%----------------------------------------------------------
|
|
byteArrayOutputStream = java.io.ByteArrayOutputStream;
|
|
% This StreamCopier is unsupported and may change at any time. OH GREAT :/
|
|
isc = InterruptibleStreamCopier.getInterruptibleStreamCopier;
|
|
isc.copyStream(inputStream,byteArrayOutputStream);
|
|
inputStream.close;
|
|
byteArrayOutputStream.close;
|
|
|
|
if in.CAST_OUTPUT
|
|
charset = '';
|
|
|
|
%Extraction of character set from Content-Type header if possible
|
|
if isfield(firstHeaders,'Content_Type')
|
|
text = firstHeaders.Content_Type;
|
|
%Always open to regexp improvements
|
|
charset = regexp(text,'(?<=charset=)[^\s]*','match','once');
|
|
end
|
|
|
|
if ~isempty(charset)
|
|
output = native2unicode(typecast(byteArrayOutputStream.toByteArray','uint8'),charset);
|
|
else
|
|
output = char(typecast(byteArrayOutputStream.toByteArray','uint8'));
|
|
end
|
|
else
|
|
%uint8 is more useful for later charecter conversions
|
|
%uint8 or int8 is somewhat arbitary at this point
|
|
output = typecast(byteArrayOutputStream.toByteArray','uint8');
|
|
end
|
|
|
|
extras = struct;
|
|
extras.allHeaders = allHeaders;
|
|
extras.firstHeaders = firstHeaders;
|
|
extras.status = status;
|
|
%Gets eventual url even with redirection
|
|
extras.url = char(urlConnection.getURL);
|
|
extras.isGood = isGood;
|
|
|
|
|
|
|
|
end
|
|
|
|
function headerNameOut = fixHeaderCasing(headerName)
|
|
%fixHeaderCasing Forces standard casing of headers
|
|
%
|
|
% headerNameOut = fixHeaderCasing(headerName)
|
|
%
|
|
% This is important for field access in a structure which
|
|
% is case sensitive
|
|
%
|
|
% Not yet finished.
|
|
% I've been adding to this function as problems come along
|
|
|
|
switch lower(headerName)
|
|
case 'location'
|
|
headerNameOut = 'Location';
|
|
case 'content_type'
|
|
headerNameOut = 'Content_Type';
|
|
otherwise
|
|
headerNameOut = headerName;
|
|
end
|
|
end
|
|
|
|
%==========================================================================
|
|
%==========================================================================
|
|
%==========================================================================
|
|
|
|
function urlConnection = getURLConnection(urlChar)
|
|
%getURLConnection
|
|
%
|
|
% urlConnection = getURLConnection(urlChar)
|
|
|
|
% Determine the protocol (before the ":").
|
|
protocol = urlChar(1:find(urlChar==':',1)-1);
|
|
|
|
|
|
% Try to use the native handler, not the ice.* classes.
|
|
try
|
|
switch protocol
|
|
case 'http'
|
|
%http://www.docjar.com/docs/api/sun/net/www/protocol/http/HttpURLConnection.html
|
|
handler = sun.net.www.protocol.http.Handler;
|
|
case 'https'
|
|
handler = sun.net.www.protocol.https.Handler;
|
|
end
|
|
catch ME
|
|
handler = [];
|
|
end
|
|
|
|
% Create the URL object.
|
|
try
|
|
if isempty(handler)
|
|
url = java.net.URL(urlChar);
|
|
else
|
|
url = java.net.URL([],urlChar,handler);
|
|
end
|
|
catch ME
|
|
error('Failure to parse URL or protocol not supported for:\nURL: %s',urlChar);
|
|
end
|
|
|
|
% Get the proxy information using MathWorks facilities for unified proxy
|
|
% preference settings.
|
|
mwtcp = com.mathworks.net.transport.MWTransportClientPropertiesFactory.create();
|
|
proxy = mwtcp.getProxy();
|
|
|
|
% Open a connection to the URL.
|
|
if isempty(proxy)
|
|
urlConnection = url.openConnection;
|
|
else
|
|
urlConnection = url.openConnection(proxy);
|
|
end
|
|
|
|
|
|
end
|