I want to create basic HTTP client from scratch using Swift for learning purpose to unserstand how the sender and receiver of http over the internet works. It's just for raw learning purpose so, I just want to go with socket. For now I am trying in the localhost, I've posted same in SO. I've tried below code
import Foundation
import Darwin
final class Client {
let host: String
let port: UInt16
init(host: String, port: UInt16) {
self.host = host
self.port = port
}
func get(_ completionHandler: @escaping() -> Void) {
// Create a socket
let sock = socket(AF_INET, SOCK_STREAM, 0)
var serveraddr = sockaddr_in()
serveraddr.sin_family = sa_family_t(AF_INET)
serveraddr.sin_port = self.port
serveraddr.sin_addr.s_addr = inet_addr(self.host)
// Connect to the server
print("Connecting....")
withUnsafePointer(to: &serveraddr) { sockaddrInPtr in
let sockaddrPtr = UnsafeRawPointer(sockaddrInPtr).assumingMemoryBound(to: sockaddr.self)
let connection = connect(sock, sockaddrPtr, socklen_t(MemoryLayout<sockaddr_in>.size))
if connection == 0 {
// Form an HTTP GET request
let request = "GET / HTTP/1.1\r\nHost: \(self.host)\r\n\r\n"
let bytes = [UInt8](request.data(using: .utf8)!)
// Send the request over the socket
send(sock, bytes, request.count, 0)
/*
// Receive and print the response
var response = [UInt8](repeating: 0, count: 1024)
let bytesRead = recv(sock, &response, response.count, 0)
if bytesRead > 0 {
if let httpResponse = String(bytes: response, encoding: .utf8) {
print(httpResponse)
}
}
// Close the socket
close(sock)
*/
} else {
print("not connected \(connection)")
}
}
}
}
Please note that this forum tries to stay focused on topics related to Swift programming language itself.
Please also note that using socket API is no longer recommended on apple platforms (don't remember if it's officially deprecated or not, it probably should if not already).
htons or its modern analogue is missing here (to convert from platform endian to big endian). In the current form the app should work with a port set to, say, 37008 (0x9090).
Once you pass that, try to run this code in a sandboxed environment and it doesn't work - don't forget to enable "network client" permission in the app's entitlement file. And once you passed that you may also need to fiddle with NSAppTransportSecurity permission to allow HTTP.
I've ran the server using the socket and it's successfully works. So the client socket should also work also no deprication warning in the xcode.
for sin_port the type is in_port_t which is public typealias in_port_t = __uint16_t. I've used 0xF01F, in_port_t(self.port). Nothing works. I am not sure what is htons.
Yes I've already set App Transport Security Settings/Allow Arbitrary Loads = YES & Removed sandboxed environment already. The same host and port address using URLSession is works perfect.
@IBAction func actionGet(_ sender: NSButton) {
URLSession.shared.dataTask(with: URL(string: "http://127.0.0.1:8080")!) { data, _, error in
if let data = data, let stringValue = String(data: data, encoding: .utf32) { // UTF32 CUSTOM_ENCODING in server side
print("======== URLSession ========")
print(stringValue)
print("==================")
print("======== Socket ======")
self.client.get {
print("GET request successfully executed!")
}
print("==================")
} else {
if let error = error {
print("Error: \(error.localizedDescription)")
} else {
print("Unknown Execption!")
}
}
}.resume()
}
Console Log:
======== URLSession ========
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 53
<h1>Hello, World <b style="color:green;">GET</b></h1>
==================
======== Socket ======
Connecting....
not connected -1
==================
htons et al is traditional unix way to convert from host endian (little on current apple platforms) to network endian (always big). (the ntohx family of functions is doing the reverse). You could achieve the same result by using:
serveraddr.sin_port = port.bigEndian
37008 works even if you got endian wrong as it is 0x9090.
What is 0xF01F? 8080 is 0x1F90 and in the big endian form would be 0x901F.
Using BSD Sockets correctly from Swift is a serious challenge. I’ve gone back and forth on this issue myself a lot, and I’ve finally settled on the approach shown in Calling BSD Sockets from Swift. As explained in that top-level post, this isn’t meant for production code but rather for test projects like the one we’re discussing here.
Oh, and implementing a correct HTTP client is ridiculously difficult, even if you limit yourself to HTTP/1.1 and no HTTPS. So, while writing your own HTTP code for a test project like this is fine, if you ever plan to deploy this I recommend that you use an existing HTTP library.
Yes I've already set App Transport Security Settings > Allow Arbitrary
Loads [to] YES
Just to be clear, ATS only applies to URLSession and above. Low-level APIs, like Network framework and BSD Sockets, just ignore ATS.
Removed sandboxed environment already.
OTOH, App Sandbox applies to all network connections, so that was the right move (-:
To expand on what Quinn wrote: if you do need sandboxed app (e.g. a UI app, not sure if this could apply to a terminal app) you could still use networking: on macOS you'd need to enable "outgoing network connections" permission (com.apple.security.network.client) or "incoming network connections" (com.apple.security.network.server), or both depending upon your needs – those are in the target's entitlements file.
Is your QSocket2023 anywhere downloadable or do we need to
copy'n'paste from your posts?
The latter.
It’s tricky, from a red tape perspective, for me to make it available as, say, a Swift package. I could probably cut through that but I don’t really want to. My latest design is tightly focused on my need to create small test projects, forums posts, and so on. In Real Code™ you’d do things differently; for example, you wouldn’t want to do name-to-address translation every time you send a UDP datagram O-:
I’d love for someone to tackle this problem properly, building a standard BSD Sockets [1] wrapper, kinda like Swift System, that everyone can use [2]. However, I’m not the person to take on that task.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
[1] Actually, you’d want it to support Linux in which case it’d be… well… just Sockets (-:
[2] Much as I think that BSD Sockets is a horrible API that should just go away, that’s not going to happen )-: