SwiftUI: URL Request over specific browser - ios

I want to retrieve a json from an intranet from outside the intranet in an iPad app. I have a VPN connection configured for this, but it only runs through the VMWare web browser. Safari uses the normal connection.
Is it possible, similar to the browser schemas, to use awbs:// instead of https:// and trigger the VPN configuration from an app? I don't want to open the browser though, I just want to use it. ;)
func getData() async {
#State var value: [responsejson]?
guard let url = URL(string: "https:/...") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(responsejson.self, from: data) {
DispatchQueue.main.async {
self.response = decodedResponse.text
}
return
}
}
print("Error: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
I tried to replace here https with awbs, but unfortunately it did not work
Task <1039D056-FDC4-46EE-9635-373AF8723409>.<1> finished with error [-1002] Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={NSLocalizedDescription=unsupported URL, NSErrorFailingURLStringKey=awbs://(...)

http,https or awbs is not just a text, but a definition of the protocol. Protocol defines how the data will be transferred, and there is a specific list of protocol that iOS support. see https://en.wikipedia.org/wiki/Internet_protocol_suite (don't be confused with app-scheme, when you open a link and specific app response to it, these are two different things).
So when you try to create an instance of a URL object it actually parses a protocol and uses instructions to make a Request.

Related

GCDWebServer: How do I change file permissions on server for WebDAV operations? (iOS)

I'm currently trying to use GCDWebServer to host a local webpage that's requested by a WKWebView when the app starts. I want to be able to upload external files to the server by grabbing files with a UIDocumentPickerViewController while the app is running. It seems like using a separate GCDWebDAVServer on a different port is a good idea for this.
However, if I try to upload a file to the WebDAV server, I get this data from the response:
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>HTTP Error 500</title></head><body><h1>HTTP Error 500: Failed moving uploaded file to "/arbitrary.txt"</h1><h3>[NSCocoaErrorDomain] “35B890AC-7CD2-4A10-A67F-BAED3D6C34AB-3278-000005593C778876” couldn’t be moved because you don’t have permission to access “www”. (513)</h3></body></html>
Cutting out this bit for readability:
Failed moving uploaded file to "/arbitrary.txt"</h1><h3>[NSCocoaErrorDomain] “35B890AC-7CD2-4A10-A67F-BAED3D6C34AB-3278-000005593C778876” couldn’t be moved because you don’t have permission to access “www”.
www in this context is the local folder I'm serving with GCDWebServer. It has no subfolders, only files.
Here's the code I'm using in viewDidLoad:
let webContentUrl = Bundle.main.path(forResource: "www", ofType: nil)!
httpServer = GCDWebServer()
webDAVURL = "http://localhost:\(WEBDAV_PORT)/"
webDAVServer = GCDWebDAVServer(uploadDirectory: webContentUrl)
httpServer.addGETHandler(forBasePath: "/", directoryPath: webContentUrl, indexFilename: "index.html", cacheAge: 3600, allowRangeRequests: true)
httpServer.start(withPort: HTTP_PORT, bonjourName: nil)
let options: [String: Any] = [
GCDWebServerOption_Port: WEBDAV_PORT
]
do {
try webDAVServer.start(options: options)
} catch let error {
print("Could not start WebDAV server. Reason: \(error.localizedDescription)")
}
let request = URLRequest(url: URL(string: "http://localhost:\(HTTP_PORT)/")!)
webView.load(request)
And the code used for a PUT request to upload a file to the WebDAV server:
let importedFileUrl = urls.first!
do {
let data = try Data(contentsOf: importedFileUrl)
var request = URLRequest(url: URL(string: "http://localhost:\(WEBDAV_PORT)/arbitrary.txt")!)
request.httpMethod = "PUT"
let task = URLSession(configuration: .ephemeral).uploadTask(with: request, from: data) { data, response, error in
print("Data: \(String(describing: data))")
print("Response: \(String(describing: response))")
print("Error: \(String(describing: error))")
print(String(decoding: data!, as: UTF8.self))
}
task.resume()
} catch let error {
createErrorAlert(message: error.localizedDescription)
}
This doesn't have anything to do with iOS 14 local network privacy features. I tried modifying the Info.plist to include the new keys, but nothing changed. It seems like the www folder doesn't have write permissions.
Is there a feature I'm missing that lets you change file permissions in GCDWebServer, or is there a workaround? Right now the only workaround I can think of is to create a local database alongside the web servers.
The reason write permissions were not allowed is because I was serving files directly from the app bundle, which cannot be written to.
My solution was to copy the contents of that directory into the Documents directory and use WebDAV to write to that directory instead.

HTTP DELETE Works From Browser But Not From Postman or IOS App

When attempting an http request to my rest api, I continually get a 401 error when using the following code. I don not get this error making any other type of request. I have provided the function that makes the request below.
func deleteEvent(id: Int){
eventUrl.append(String(id))
let request = NSMutableURLRequest(url: NSURL(string: eventUrl)! as URL)
request.httpMethod = "DELETE"
print(eventUrl)
eventUrl.removeLast()
print(self.token!)
request.allHTTPHeaderFields = ["Authorization": "Token \(self.token)"]
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
if error != nil {
print("error=\(String(describing: error))")
//put variable that triggers error try again view here
return
}
print("response = \(String(describing: response))")
}
task.resume()
}
When sending the delete request with postman, the rest api just returns the data I want to delete but does not delete it. For reference I have posted the view and permissions classes associated with this request Any help understanding why this may be resulting in an error is greatly appreciated!
Views.py
class UserProfileFeedViewSet(viewsets.ModelViewSet):
"""Handles creating, reading and updating profile feed items"""
authentication_classes = (TokenAuthentication,)
serializer_class = serializers.ProfileFeedItemSerializer
queryset = models.ProfileFeedItem.objects.all()
permission_classes = (permissions.UpdateOwnStatus, IsAuthenticated)
def perform_create(self, serializer):
"""Sets the user profile to the logged in user"""
#
serializer.save(user_profile=self.request.user)
Permissions.py
class UpdateOwnStatus(permissions.BasePermission):
"""Allow users to update their own status"""
def has_object_permission(self, request, view, obj):
"""Check the user is trying to update their own status"""
if request.method in permissions.SAFE_METHODS:
return True
return obj.user_profile.id == request.user.id
HEADER SENT WITH DELETE REQUEST VIA POSTMAN
Preface: You leave out too much relevant information from the question for it to be properly answered. Your Swift code looks, and please don't be offended, a bit beginner-ish or as if it had been migrated from Objective-C without much experience.
I don't know why POSTMAN fails, but I see some red flags in the Swift code you might want to look into to figure out why your iOS app fails.
I first noticed that eventUrl seems to be a String property of the type that contains the deleteEvent function. You mutate it by appending the event id, construct a URL from it (weirdly, see below), then mutate it back again. While this in itself is not necessarily wrong, it might open the doors for racing conditions depending how your app works overall.
More importantly: Does your eventUrl end in a "/"? I assume your DELETE endpoint is of the form https://somedomain.com/some/path/<id>, right? Now if eventUrl just contains https://somedomain.com/some/path your code constructs https://somedomain.com/some/path<id>. The last dash is missing, which definitely throws your backend off (how I cannot say, as that depends how the path is resolved in your server app).
It's hard to say what else is going from from the iOS app, but other than this potential pitfall I'd really recommend using proper Swift types where possible. Here's a cleaned up version of your method, hopefully that helps you a bit when debugging:
func deleteEvent(id: Int) {
guard let baseUrl = URL(string: eventUrl), let token = token else {
// add more error handling code here and/or put a breakpoint here to inspect
print("Could not create proper eventUrl or token is nil!")
return
}
let deletionUrl = baseUrl.appendingPathComponent("\(id)")
print("Deletion URL with appended id: \(deletionUrl.absoluteString)")
var request = URLRequest(url: deletionUrl)
request.httpMethod = "DELETE"
print(token) // ensure this is correct
request.allHTTPHeaderFields = ["Authorization": "Token \(token)"]
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Encountered network error: \(error)")
return
}
if let httpResponse = response as? HTTPURLResponse {
// this is basically also debugging code
print("Endpoint responded with status: \(httpResponse.statusCode)")
print(" with headers:\n\(httpResponse.allHeaderFields)")
}
// Debug output of the data:
if let data = data {
let payloadAsSimpleString = String(data: data, encoding: .utf8) ?? "(can't parse payload)"
print("Response contains payload\n\(payloadAsSimpleString)")
}
}
task.resume()
}
This is obviously still limited in terms of error handling, etc., but a little more swifty and contains more console output that will hopefully be helpful.
The last important thing is that you have to ensure iOS does not simply block your request due to Apple Transport Security: Make sure your plist has the expected entries if needed (see also here for a quick intro).

URLSession.Datatask returns 0 bytes of data

Trying to figure this one out, I'm stumped. When making a REST call to get json data back from a response (GET or POST, each should return data) I get back 0 bytes.
This is pre-serialization. The POST successfully creates a message on the backend, and the backend shows a response being sent; with charles proxy on, I've confirmed that there is a response with valid JSON data.
Any ideas why this would be failing ONLY in iOS? Postman/Charles proxy (from the iOS calls!) shows valid data in the response, but the debugger picks up nothing.
Thanks in advance for anything thoughts.
let components = URLComponents(string: "mysuperValidURL.com")
guard let url = components?.url else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
setUrlRequestToken(request: &request)
let message = ChatMessage(content: message, group: group, userId: userId)
let jsonEncoder = JSONEncoder()
guard let data = try? jsonEncoder.encode(message) else {
return
}
URLSession.shared.uploadTask(with: request, from: data) { (data, response, error) in
// Here there be 0 bytes
}.resume()
}
Data will sometimes come back as 0 bytes in the debugger; add a print with debug description to ensure you're getting data. In this case it was a failure of the debugger mixed with a later serialization error that caused it to appear to be broken.
TLDR; don't trust the realtime debugger, use some prints to sanity check.

iOS/Swift & Coinbase Pro API - Subscribe to Websocket Feed

I am attempting to subscribe to a websocket feed using Swift. Per the new Coinbase Pro API documentation for their websocket feed:
To begin receiving feed messages, you must first send a subscribe message to the server indicating which channels and products to receive. This message is mandatory — you will be disconnected if no subscribe has been received within 5 seconds.
The first thing I did was add Starscream to the project to make connecting to websockets easier to implement. I followed the steps on the README and added the delegate methods appropriately.
Next, I successfully sent an HTTP GET request (I get a 200 code in response) by creating a URLSession object and calling dataTask(with: ) after setting up a request, like so:
let session = URLSession.shared
guard let url = URL(string: "https://api.pro.coinbase.com/users/self/verify") else {
print("Could not create URL.")
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
let body: [String: Any] = ["type": "subscribe",
"channels": [["name": "heartbeat"],
["product_ids": ["BTC-USD"]]]]
let data = try! JSONSerialization.data(withJSONObject: body,
options: JSONSerialization.WritingOptions.sortedKeys)
request.httpBody = data
let task = session.dataTask(with: request) { (data, response, error) in
// Check for errors, clean up data, etc.
}
task.resume
Everything appears to be linked up correctly, but I am still not receiving the "subscription" messages from the websocket feed. What am I missing?

iOS Swift unsupported URL

I have a REST web service running on tomcat at 92.126.230.210:9090/delete(this is a fake ip but the structure is the same) that I want to consume but the compiler output tells to me "unsupported URL".
I have no spaces and all characters are in the ASCII table.
WS call:
//uriParam = 92.126.230.210:9090/delete?uid=SAFAKEWDW
func deleteUserRESTCall(uri uriParam: String){
URLSession.shared.dataTask(with: URL(string: uriParam)!) { data, response, error in
if error != nil {
print(error!.localizedDescription)
}
}.resume()
}
The URL is missing a protocol, e.g. https://92.126.230.210:9090/delete?uid=SAFAKEWDW.

Resources