Discover ways to construct a quite simple file add API server utilizing Vapor 4 and URLSession add process on the shopper facet.
A easy file add server written in Swift
For this easy file add tutorial we’ll solely use the Vapor Swift bundle as a dependency. 📦
// swift-tools-version:5.3
import PackageDescription
let bundle = Package deal(
identify: "myProject",
platforms: [
.macOS(.v10_15)
],
dependencies: [
.package(url: "https://github.com/vapor/vapor", from: "4.35.0"),
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Vapor", package: "vapor"),
],
swiftSettings: [
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .launch))
]
),
.goal(identify: "Run", dependencies: [.target(name: "App")]),
.testTarget(identify: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
You possibly can setup the mission with the required information utilizing the Vapor toolbox, alternatively you possibly can create every little thing by hand utilizing the Swift Package deal Supervisor, lengthy story quick, we simply want a starter Vapor mission with out further dependencies. Now in case you open the Package deal.swift file utilizing Xcode, we are able to setup our routes by altering the configure.swift
file.
import Vapor
public func configure(_ app: Software) throws {
/// allow file middleware
app.middleware.use(FileMiddleware(publicDirectory: app.listing.publicDirectory))
/// set max physique dimension
app.routes.defaultMaxBodySize = "10mb"
/// setup the add handler
app.publish("add") { req -> EventLoopFuture in
let key = strive req.question.get(String.self, at: "key")
let path = req.software.listing.publicDirectory + key
return req.physique.gather()
.unwrap(or: Abort(.noContent))
.flatMap { req.fileio.writeFile($0, at: path) }
.map { key }
}
}
First we use the FileMiddleware
, this can enable us to server information utilizing the Public listing inside our mission folder. If you happen to don’t have a listing named Public, please create one, for the reason that file add server will want that. Don’t overlook to provide correct file system permissions if vital, in any other case we gained’t be capable to write our information contained in the listing. 📁
The subsequent factor that we set is the default most physique dimension. This property can restrict the quantity of knowledge that our server can settle for, you don’t actually wish to use this technique for big information as a result of uploaded information can be saved within the system reminiscence earlier than we write them to the disk.
If you wish to add giant information to the server you need to think about streaming the file as a substitute of gathering the file information from the HTTP physique. The streaming setup would require a bit extra work, however it’s not that sophisticated, in case you are thinking about that resolution, you need to learn the Information API and the physique streaming part utilizing official Vapor docs website.
This time we simply desire a lifeless easy file add API endpoint, that collects the incoming information utilizing the HTTP physique right into a byte buffer object, then we merely write this buffer utilizing the fileio to the disk, utilizing the given key from the URL question parameters. If every little thing was completed with out errors, we are able to return the important thing for the uploaded file.
File add duties utilizing the URLSession API
The Basis frameworks provides us a pleasant API layer for widespread networking duties. We will use the URLSession uploadTask technique to ship a brand new URLRequest with a knowledge object to a given server, however IMHO this API is sort of unusual, as a result of the URLRequest object already has a httpBody property, however you must explicitly move a “from: Knowledge?” argument whenever you assemble the duty. However why? 🤔
import Basis
extension URLSession {
func uploadTask(with request: URLRequest, completionHandler: @escaping (Knowledge?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask {
uploadTask(with: request, from: request.httpBody, completionHandler: completionHandler)
}
}
Anyway, I made a bit extension technique, so after I create the URLRequest I can set the httpBody property of it and safely move it earlier than the completion block and use the contents because the from parameter. Very unusual API design selection from Apple… 🤐
We will put this little snippet right into a easy executable Swift bundle (or after all we are able to create a complete software) to check our add server. In our case I’ll place every little thing right into a most important.swift
file.
import Basis
import Dispatch
extension URLSession {
func uploadTask(with request: URLRequest, completionHandler: @escaping (Knowledge?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask {
uploadTask(with: request, from: request.httpBody, completionHandler: completionHandler)
}
}
let fileData = strive Knowledge(contentsOf: URL(fileURLWithPath: "/Customers/[user]]/[file].png"))
var request = URLRequest(url: URL(string: "http://localhost:8080/add?key=(UUID().uuidString).png")!)
request.httpMethod = "POST"
request.httpBody = fileData
let process = URLSession.shared.uploadTask(with: request) { information, response, error in
guard error == nil else {
fatalError(error!.localizedDescription)
}
guard let response = response as? HTTPURLResponse else {
fatalError("Invalid response")
}
guard response.statusCode == 200 else {
fatalError("HTTP standing error: (response.statusCode)")
}
guard let information = information, let end result = String(information: information, encoding: .utf8) else {
fatalError("Invalid or lacking HTTP information")
}
print(end result)
exit(0)
}
process.resume()
dispatchMain()
The above instance makes use of the Dispatch
framework to attend till the asynchronous file add finishes. You need to change the situation (and the extension) of the file if vital earlier than you run this script. Since we outlined the add route as a POST endpoint, now we have to set the httpMethod
property to match this, additionally we retailer the file information within the httpBody variable earlier than we create our process. The add URL ought to comprise a key, that the server can use as a reputation for the file. You possibly can add extra properties after all or use header values to test if the person has correct authorization to carry out the add operation. Then we name the add process extension technique on the shared URLSession property. The great factor about uploadTask is you could run them on the background if wanted, that is fairly useful if it involves iOS growth. 📱
Contained in the completion handler now we have to test for just a few issues. To begin with if there was an error, the add should have failed, so we name the fatalError technique to interrupt execution. If the response was not a legitimate HTTP response, or the standing code was not okay (200) we additionally cease. Lastly we wish to retrieve the important thing from the response physique so we test the information object and convert it to a UTF8 string if attainable. Now we are able to use the important thing mixed with the area of the server to entry the uploaded file, this time I simply printed out the end result, however hey, that is only a demo, in an actual world software you would possibly wish to return a JSON response with further information. 😅
Vanilla JavaScript file uploader
Yet one more factor… you need to use Leaf and a few Vanilla JavaScript to add information utilizing the newly created add endpoint. Really it’s very easy to implement a brand new endpoint and render a Leaf template that does the magic. You’ll want some primary HTML and some traces of JS code to submit the contents of the file as an array buffer. It is a primary instance.
File add
As you possibly can see it’s an ordinary XHR
request mixed with the FileReader JavaScript API. We use the FileReader to transform our enter to a binary information, this fashion our server can write it to the file system within the anticipated format. Generally individuals are utilizing a multipart-encoded kind to entry information on the server, however when you must work with an API you may also switch uncooked file information. If you wish to study extra about XHR requests and AJAX calls, you need to learn my earlier article.
I even have a publish about completely different file add strategies utilizing normal HTML kinds and a Vapor 4 server as a backend. I hope you’ll discover the correct resolution that you just want to your software. 👍