unit ns_session;
{$mode objfpc}{$H+}
{$modeswitch objectivec1}
{$modeswitch cblocks}
interface
uses
SysUtils, // for IntToStr()
MacOSAll,
CocoaAll; // for NSData and other Cocoa types
type
// setup cBlock for completion handler
tblock = reference to procedure(data: NSData; response: NSURLResponse; connectionError: NSError); cdecl; cblock;
// redefine version from packages/cocoaint/src/foundation/NSURLSession.inc
NSURLSession = objcclass external (NSObject)
public
class function sessionWithConfiguration(configuration: NSURLSessionConfiguration): NSURLSession; message 'sessionWithConfiguration:';
end;
NSURLSessionAsynchronousConvenience = objccategory external (NSURLSession)
function dataTaskWithURL_completionHandler(url: NSURL; completionHandler: tBlock): NSURLSessionDataTask; message 'dataTaskWithURL:completionHandler:';
end;
{ TNSHTTPSendAndReceive }
TNSHTTPSendAndReceive = class(TObject)
private
procedure myCompletionHandler(data: NSData; response: NSURLResponse; connectionError: NSError);
public
procedure URlReq(Address: AnsiString);
end;
var
myCache: NSURLcache;
webData: String;
webResponse: String;
webHTML: String;
webStatusCode: Integer;
webError: String;
webErrorReason: String;
didFinish: PRTLEvent;
implementation
function CFStrToAnsiStr(cfStr : CFStringRef;
encoding : CFStringEncoding = kCFStringEncodingWindowsLatin1): AnsiString;
{Convert CFString to AnsiString.
If encoding is not specified, encode using CP1252.}
var
StrPtr : Pointer;
StrRange : CFRange;
StrSize : CFIndex;
begin
if cfStr = nil then
begin
Result := '';
Exit;
end;
{First try the optimized function}
StrPtr := CFStringGetCStringPtr(cfStr, encoding);
if StrPtr <> nil then {Succeeded?}
Result := PChar(StrPtr)
else {Use slower approach - see comments in CFString.pas}
begin
StrRange.location := 0;
StrRange.length := CFStringGetLength(cfStr);
{Determine how long resulting string will be}
CFStringGetBytes(cfStr, StrRange, encoding, Ord('?'),
False, nil, 0, StrSize);
SetLength(Result, StrSize); {Expand string to needed length}
if StrSize > 0 then {Convert string?}
CFStringGetBytes(cfStr, StrRange, encoding, Ord('?'),
False, @Result[1], StrSize, StrSize);
end;
end; {CFStrToAnsiStr}
function NSStrToStr(aNSStr : NSString;
encoding : CFStringEncoding = kCFStringEncodingWindowsLatin1): string;
{Convert NSString to string.
If encoding is not specified, encode using CP1252.
This assumes string = AnsiString.}
begin
{Note NSString and CFStringRef are interchangable}
Result := CFStrToAnsiStr(CFStringRef(aNSStr), encoding);
end;
// Completion handler: Executed after URL has been retrieved or retrieval fails
procedure TNSHTTPSendAndReceive.myCompletionHandler(data: NSData; response: NSURLResponse; connectionError: NSError);
var
httpResponse: NSHTTPURLResponse;
begin
{$IFDEF DEBUG}
NSLog(NSStr('Completion handler called'));
if (NSThread.currentThread.isMainThread) then
NSLog(NSStr('In main thread--completion handler'))
else
NSLog(NSStr('Not in main thread--completion handler'));
{$ENDIF}
// if no error
if((data.Length > 0) and (connectionError = Nil)) then
begin
{$IFDEF DEBUG}
NSLog(NSStr('Data desciption: %@'),data.description);
NSLog(NSStr('Response description: %@'),response.description);
NSLog(NSStr('Data: %@'),NSString.alloc.initWithData_encoding(data,NSASCIIStringEncoding).autorelease);
{$ENDIF}
webData := NSStrToStr(data.description);
{$IFDEF DEBUG}
NSLog(NSStr('Web data' + webData));
{$ENDIF}
// The NSHTTPURLResponse class is a subclass of NSURLResponse
// so we can cast an NSURLResponse as an NSHTTPURLResponse
httpResponse := NSHTTPURLResponse(response);
{$IFDEF DEBUG}
// Extract status code from response header
NSLog(NSStr('HTTP status code: %@'),NSStr(IntToStr(httpResponse.statusCode)));
{$ENDIF}
webStatusCode := httpResponse.statusCode;
webResponse := NSStrToStr(response.description);
webHTML := NSStrToStr(NSString.alloc.initWithData_encoding(data,NSASCIIStringEncoding));
end
// o/w return error
else
begin
{$IFDEF DEBUG}
NSLog(NSStr('Error %@'), connectionError.userInfo);
{$ENDIF}
webError := 'Error description: ' + LineEnding + NSStrToStr(connectionError.description);
webErrorReason := 'Error retrieving: ' + NSStrToStr(connectionError.userInfo.valueForKey(NSErrorFailingUrlStringKey))
+ LineEnding + LineEnding + 'Reason: ' + NSStrToStr(connectionError.localizedDescription);
end;
// notify main thread that completion handler thread has finished
RTLEventSetEvent(didFinish);
{$IFDEF DEBUG}
NSLog(NSStr('leaving handler'));
{$ENDIF}
end;
procedure TNSHTTPSendAndReceive.URlReq(Address: AnsiString);
var
urlSessionConfig: NSURLSessionConfiguration = Nil;
cachePath: NSString = Nil;
urlSession: NSURLSession = Nil;
URL: NSURL = Nil;
begin
// create event to synchronise completion handler background thread
// with the GUI main thread
didFinish := RTLEventCreate;
// create default url session config
urlSessionConfig := NSURLSessionConfiguration.defaultSessionConfiguration;
// configure caching behavior for the default session
cachePath := NSTemporaryDirectory.stringByAppendingPathComponent(NSStr('/nsurlsessiondemo.cache'));
{$IFDEF DEBUG}
NSLog(NSStr('Cache path: %@'), cachePath);
{$ENDIF}
myCache := NSURLCache.alloc.initWithMemoryCapacity_diskCapacity_diskPath(16384, 268435456, cachePath);
urlSessionConfig.setURLCache(myCache);
// set cache policy
{$IFDEF DEBUG}
urlSessionConfig.setRequestCachePolicy(NSURLRequestReloadIgnoringLocalCacheData);
{$ELSE}
urlSessionConfig.setRequestCachePolicy(NSURLRequestUseProtocolCachePolicy);
{$ENDIF}
// create a session for configuration
urlSession := NSURLSession.sessionWithConfiguration(urlSessionConfig);
// create NSURL
URL := NSURL.URLWithString(NSSTR(Address));
if(Url = Nil) then
writeln('NSURL.URLWithString failed!');
// setup and execute data task
urlSession.dataTaskWithURL_completionHandler(URL, @myCompletionHandler).resume;
// wait for completion handler to finish
RTLEventWaitFor(didFinish);
// display results
if(webErrorReason <> '') then
begin
writeln(webError);
writeln(webErrorReason);
end
else
begin
writeln('HTTP status code: ' + IntToStr(webStatusCode) + LineEnding
+ LineEnding + 'Raw data: ' + LineEnding + webData);
writeln('Response: ' + LineEnding + LineEnding + webResponse);
writeln('Web page: ' + LineEnding + LineEnding + webHTML);
end;
// housekeeping
RTLeventdestroy(didFinish);
myCache.release;
end;
end.