Skip to content

Commit

Permalink
ios: update demos to display errors (#192)
Browse files Browse the repository at this point in the history
Update the Swift and Objective-C demos to follow the UI logic implemented by envoyproxy/envoy-mobile#166.

Resolves envoyproxy/envoy-mobile#168.

![Simulator Screen Shot - TestDevice - 2019-06-26 at 18 09 50](https://user-images.githubusercontent.com/2643476/60225905-a890ce00-983d-11e9-93ea-5e2c77829413.png)

Signed-off-by: Michael Rebello <[email protected]>
Signed-off-by: JP Simard <[email protected]>
  • Loading branch information
rebello95 authored and jpsim committed Nov 29, 2022
1 parent fe8e671 commit 7bb7d6c
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 76 deletions.
1 change: 0 additions & 1 deletion mobile/.swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ whitelist_rules:
- force_unwrapping
- generic_type_name
- identical_operands
- identifier_name
- implicit_getter
- inert_defer
- is_disjoint
Expand Down
147 changes: 100 additions & 47 deletions mobile/examples/objective-c/hello_world/ViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,29 @@

#pragma mark - Constants

NSString* _CELL_ID = @"cell-id";
NSString* _ENDPOINT = @"http://localhost:9001/api.lyft.com/static/demo/hello_world.txt";
NSString *_CELL_ID = @"cell-id";
NSString *_ENDPOINT = @"http://localhost:9001/api.lyft.com/static/demo/hello_world.txt";

#pragma mark - ResponseValue
#pragma mark - Result

/// Represents a response from the server.
@interface ResponseValue : NSObject
@property (nonatomic, strong) NSString* body;
@property (nonatomic, strong) NSString* serverHeader;
/// Represents a response from the server or an error.
@interface Result : NSObject
@property (nonatomic, assign) int identifier;
@property (nonatomic, strong) NSString *body;
@property (nonatomic, strong) NSString *serverHeader;
@property (nonatomic, strong) NSString *error;
@end

@implementation ResponseValue
@implementation Result
@end

#pragma mark - ViewController

@interface ViewController ()
@property (nonatomic, strong) NSMutableArray<ResponseValue*>* responses;
@property (nonatomic, weak) NSTimer* requestTimer;
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, assign) int requestCount;
@property (nonatomic, strong) NSMutableArray<Result *> *results;
@property (nonatomic, weak) NSTimer *requestTimer;
@end

@implementation ViewController
Expand All @@ -31,8 +35,14 @@ @implementation ViewController
- (instancetype)init {
self = [super init];
if (self) {
self.responses = [NSMutableArray new];
self.tableView.allowsSelection = FALSE;
NSURLSessionConfiguration *configuration =
[NSURLSessionConfiguration defaultSessionConfiguration];
configuration.URLCache = nil;
configuration.timeoutIntervalForRequest = 10;
self.session = [NSURLSession sessionWithConfiguration:configuration];

self.results = [NSMutableArray new];
self.tableView.allowsSelection = NO;
}
return self;
}
Expand All @@ -59,66 +69,109 @@ - (void)startRequests {
}

- (void)performRequest {
NSURLSession* session = [NSURLSession sharedSession];
// Note that the request is sent to the envoy thread listening locally on port 9001.
NSURL* url = [NSURL URLWithString:_ENDPOINT];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
NSURL *url = [NSURL URLWithString:_ENDPOINT];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSLog(@"Starting request to '%@'", url.path);

__weak ViewController* weakSelf = self;
NSURLSessionDataTask* task =
[session dataTaskWithRequest:request
completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) {
if (error == nil && [(NSHTTPURLResponse*)response statusCode] == 200) {
[weakSelf handleResponse:(NSHTTPURLResponse*)response data:data];
} else {
NSLog(@"Received error: %@", error);
}
}];
self.requestCount++;
int requestID = self.requestCount;

__weak ViewController *weakSelf = self;
NSURLSessionDataTask *task =
[self.session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf handleResponse:(NSHTTPURLResponse *)response
data:data
identifier:requestID
error:error];
});
}];
[task resume];
}

- (void)handleResponse:(NSHTTPURLResponse*)response data:(NSData*)data {
NSString* body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (body == nil || response == nil) {
NSLog(@"Failed to deserialize response string");
- (void)handleResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
identifier:(int)identifier
error:(NSError *)error {
if (error != nil) {
NSLog(@"Received error: %@", error);
[self addResponseBody:nil serverHeader:nil identifier:identifier error:error.description];
return;
}

// Deserialize the response, which will include a `Server` header set by Envoy.
ResponseValue* value = [ResponseValue new];
value.body = body;
value.serverHeader = [[response allHeaderFields] valueForKey:@"Server"];
if (response.statusCode != 200) {
NSString *message = [NSString stringWithFormat:@"failed with status: %ld", response.statusCode];
[self addResponseBody:nil serverHeader:nil identifier:identifier error:message];
return;
}

NSString *body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (body == nil) {
NSString *message = @"failed to deserialize body";
[self addResponseBody:nil serverHeader:nil identifier:identifier error:message];
return;
}

// Deserialize the response, which will include a `Server` header set by Envoy.
NSString *serverHeader = [[response allHeaderFields] valueForKey:@"Server"];
NSLog(@"Response:\n%ld bytes\n%@\n%@", data.length, body, [response allHeaderFields]);
dispatch_async(dispatch_get_main_queue(), ^{
[self.responses addObject:value];
[self.tableView reloadData];
});
[self addResponseBody:body serverHeader:serverHeader identifier:identifier error:nil];
}

- (void)addResponseBody:(NSString *)body
serverHeader:(NSString *)serverHeader
identifier:(int)identifier
error:(NSString *)error {
Result *result = [Result new];
result.identifier = identifier;
result.body = body;
result.serverHeader = serverHeader;
result.error = error;

[self.results insertObject:result atIndex:0];
[self.tableView reloadData];
}

#pragma mark - UITableView

- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}

- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
return self.responses.count;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.results.count;
}

- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:_CELL_ID];
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_CELL_ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:_CELL_ID];
}

ResponseValue* response = self.responses[indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:@"Response: %@", response.body];
cell.detailTextLabel.text =
[NSString stringWithFormat:@"'Server' header: %@", response.serverHeader];
Result *result = self.results[indexPath.row];
if (result.error == nil) {
cell.textLabel.text =
[NSString stringWithFormat:@"[%d] %@", result.identifier, result.body];
cell.detailTextLabel.text =
[NSString stringWithFormat:@"'Server' header: %@", result.serverHeader];

cell.textLabel.textColor = [UIColor blackColor];
cell.detailTextLabel.textColor = [UIColor blackColor];
cell.contentView.backgroundColor = [UIColor whiteColor];
} else {
cell.textLabel.text =
[NSString stringWithFormat:@"[%d]", result.identifier];
cell.detailTextLabel.text = result.error;

cell.textLabel.textColor = [UIColor whiteColor];
cell.detailTextLabel.textColor = [UIColor whiteColor];
cell.contentView.backgroundColor = [UIColor redColor];
}

return cell;
}

Expand Down
5 changes: 0 additions & 5 deletions mobile/examples/swift/hello_world/Response.swift

This file was deleted.

12 changes: 12 additions & 0 deletions mobile/examples/swift/hello_world/ResponseModels.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// Represents a response from the server.
struct Response {
let id: Int
let body: String
let serverHeader: String
}

/// Error that was encountered when executing a request.
struct RequestError: Error {
let id: Int
let message: String
}
79 changes: 56 additions & 23 deletions mobile/examples/swift/hello_world/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ private let kCellID = "cell-id"
private let kURL = URL(string: "http://localhost:9001/api.lyft.com/static/demo/hello_world.txt")!

final class ViewController: UITableViewController {
private var responses = [Response]()
private let session: URLSession = {
let configuration = URLSessionConfiguration.default
configuration.urlCache = nil
configuration.timeoutIntervalForRequest = 10
return URLSession(configuration: configuration)
}()
private var requestCount = 0
private var results = [Result<Response, RequestError>]()
private var timer: Timer?

override func viewDidLoad() {
Expand All @@ -27,25 +34,35 @@ final class ViewController: UITableViewController {
}

private func performRequest() {
// Note that the request is sent to the envoy thread listening locally on port 9001.
self.requestCount += 1
let requestID = self.requestCount
let request = URLRequest(url: kURL)

// Note that the request is sent to the envoy thread listening locally on port 9001.
NSLog("Starting request to '\(kURL.path)'")
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
if let response = response as? HTTPURLResponse, response.statusCode == 200, let data = data {
self?.handle(response: response, with: data)
} else if let error = error {
NSLog("Received error: \(error)")
} else {
NSLog("Failed to receive data from the server")
self.session.dataTask(with: request) { [weak self] data, response, error in
DispatchQueue.main.async {
self?.handle(response: response, with: data, error: error, id: requestID)
}
}.resume()
}

private func handle(response: URLResponse?, with data: Data?, error: Error?, id: Int) {
if let error = error {
return self.add(result: .failure(RequestError(id: id, message: "\(error)")))
}

task.resume()
}
guard let response = response as? HTTPURLResponse, let data = data else {
return self.add(result: .failure(RequestError(id: id, message: "missing response data")))
}

guard response.statusCode == 200 else {
let error = RequestError(id: id, message: "failed with status \(response.statusCode)")
return self.add(result: .failure(error))
}

private func handle(response: HTTPURLResponse, with data: Data) {
guard let body = String(data: data, encoding: .utf8) else {
return NSLog("Failed to deserialize response string")
return self.add(result: .failure(RequestError(id: id, message: "failed to deserialize body")))
}

let untypedHeaders = response.allHeaderFields
Expand All @@ -54,14 +71,14 @@ final class ViewController: UITableViewController {
return (header.key as? String ?? String(describing: header.key), "\(header.value)")
})

// Deserialize the response, which will include a `Server` header set by Envoy.
NSLog("Response:\n\(data.count) bytes\n\(body)\n\(headers)")
self.add(result: .success(Response(id: id, body: body, serverHeader: headers["Server"] ?? "")))
}

// Deserialize the response, which will include a `Server` header set by Envoy.
let value = Response(body: body, serverHeader: headers["Server"] ?? "")
DispatchQueue.main.async {
self.responses.append(value)
self.tableView.reloadData()
}
private func add(result: Result<Response, RequestError>) {
self.results.insert(result, at: 0)
self.tableView.reloadData()
}

// MARK: - UITableView
Expand All @@ -71,7 +88,7 @@ final class ViewController: UITableViewController {
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.responses.count
return self.results.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
Expand All @@ -80,9 +97,25 @@ final class ViewController: UITableViewController {
let cell = tableView.dequeueReusableCell(withIdentifier: kCellID) ??
UITableViewCell(style: .subtitle, reuseIdentifier: kCellID)

let response = self.responses[indexPath.row]
cell.textLabel?.text = "Response: \(response.body)"
cell.detailTextLabel?.text = "'Server' header: \(response.serverHeader)"
let result = self.results[indexPath.row]
switch result {
case .success(let response):
cell.textLabel?.text = "[\(response.id)] \(response.body)"
cell.detailTextLabel?.text = "'Server' header: \(response.serverHeader)"

cell.textLabel?.textColor = .black
cell.detailTextLabel?.textColor = .black
cell.contentView.backgroundColor = .white

case .failure(let error):
cell.textLabel?.text = "[\(error.id)]"
cell.detailTextLabel?.text = error.message

cell.textLabel?.textColor = .white
cell.detailTextLabel?.textColor = .white
cell.contentView.backgroundColor = .red
}

return cell
}
}

0 comments on commit 7bb7d6c

Please sign in to comment.