Rest Api Load Testing File Upload Folder

Welcome to a new, hopefully exciting tutorial! In a previous mail I showed to you the process of creating a custom class that manages web requests and RESTful APIs. Today, we will keep building on it, as I would like to focus on a specific utilize case: How to upload files to a server!

Uploading files might non be one of the almost common things when dealing with web services. Yet, it can exist proved to be a tedious task to perform when it's time to send files to a server. In the implementation steps that follow nosotros volition try to suspension things down and shed lite to the key points and the details of the uploading process. Before we become there though, information technology'south necessary to have a quick word about some ground knowledge that we all should have on this topic.

A Quick Intro To "Multipart/form-data" Content Type

Before we first doing actual work, it'south necessary some important things to be mentioned beginning. Let me start by saying that in society to upload files to a server, multipart/form-data is the content type that should be specified in the web request. This content type allows to ship files or large amounts of data in combination with other usual data that should exist posted. "Multipart/course-information" content type tells to HTTP request that posted data should be cleaved into parts, equally if they were to be posted by a web class that expects from users to fill in various fields and select files that should be submitted to a server.

Since posted information is broken into parts, it'southward necessary for the server to know where a role starts and where information technology ends. For that purpose, a special and unique string is provided forth with the content type, called boundary. That string should not occur in the actual data, so information technology must be every bit much unique equally possible. It e'er starts with ii dashes ("–"), with an arbitrary combination of other alphanumeric characters coming later. Ordinarily, boundaries start with multiple dashes, and then they have an alphanumeric suffix (due east.k. —————–abc123).

Each part of a multipart body necessarily starts with a Content-Disposition header, with the form-data value coming in pair with it. An attribute called "proper name" should also be provided in the header, as it specifies the proper name of the part. Notice that names don't need to be unique, and sometimes server sets the rules that utilize to the "name" attribute. These two key-value pairs are enough when adding unmarried data (meaning no files) to the request's HTTP body. When appending files data, the filename should be also included in the "Content-Disposition" header with the original name of the file, equally well as the content type (MIME type) of each file that is about to exist uploaded.

The following is a fake example of a HTTP request trunk that uses the "multipart/form-data" content blazon:

Detect how everything mentioned in the previous paragraphs is used. At starting time, the "multipart/form-data" content type is specified along with the purlieus string that separates the data parts. Meet how boundary indicates the beginning of each office and also come across how semicolon (";") separates attributes in headers. Line breaks are also important when building a HTTP body such the above i. In single fields, an empty line exists between the "Content-Disposition" header and the actual field value, while the boundary of the next part comes correct afterward in the next line. In file parts, the "filename" attribute contains the name of the file, while an additional empty line exists between the file contents and the next boundary. The body ending is highlighted past the boundary, plus two more than dashes as a suffix to it.

I am encouraging you lot to take a wait at the W3C HTML Specification and read more almost encoding content types and the "multipart/class-information" particularly. You lot don't have to terminate there of form; a full general search on the web will render lots of resource to read about this topic.

Nigh The Demo App

So, as I said in the beginning of this mail service, we are going to keep edifice on the custom class nosotros created in the previous tutorial, chosen RestManager. To get started, please download a starter package which contains a Xcode projection with that class and one more directory with a demo server implementation (see next part). In Xcode project you lot will observe 3 files that nosotros'll use to test file uploading later on nosotros stop all implementation steps:

  • A text file named SampleText.txt with "lorem ipsum" data generated hither.
  • A PDF file named SamplePDF.pdf taken from File Examples.
  • An image file named SampleImage.jpg downloaded from Pexels (Photo by Oleg Magni from Pexels).

No UI will be in our app, and the results of our final tests will be printed in Xcode console and in Concluding. Whatsoever input values will be hard-coded. Therefore, we'll entirely focus on the file uploading feature that nosotros'll add to the RestManager form. Obviously, you lot are gratis to create any UI yous want if you desire to create a more dynamic demo application.

About The Server

After we cease implementing all the new code we'll run into in the following parts, we'll demand to exam if file uploading is actually working. For that purpose, a unproblematic server implemented in Node.js is included in the starter package that yous downloaded; you will observe it in the Server subdirectory. You can proceed it in the location that currently is, or copy it anywhere else you desire in your deejay.

In club to run the server, you must accept Node.js installed on your computer. If you don't, please check here or hither on how to do that. Open up Concluding and type the following command:

There is a infinite character after the cd command. Then switch to Finder, and drag and drop the Server directory to terminal and press the Return key:

By doing so, y'all don't take to type the path to the server directory; it'south automatically appended to the command in terminal.

To verify that you are successfully in the server directory, simply type:

This command volition evidence the current directory contents, and if you see something similar to the adjacent 1, and so you're just fine:

To start the server only type:

Y'all should see the message:

Server started successfully on port 3000!

The server is now running at address http://localhost:3000. You can likewise verify that if you lot paste that address in a new tab in your browser. You'll see a message coming from the server.

Notation: If you are already running another server at port 3000, edit the alphabetize.js file and set a custom port number to the port variable. So restart the server with the node index.js command.

Requests made to "http" addresses are not allowed by default in iOS as they are considered insecure. However, for the sake of the tutorial, localhost has been whitelisted in the Info.plist file of the starter project so you will meet no problem in testing the app afterwards.

Representing Files

The offset thing we need to take care of is how files are going to be represented in the RestManager form. For any file that is nearly to be uploaded, we demand to have the following data available at the fourth dimension of the HTTP body grooming:

  • The bodily file contents.
  • The original file name. Remember that the filename aspect must be in the "Content-Disposition" header of each function that represents a file.
  • The part's proper name for the name aspect in the "Content-Disposition" header.
  • The content blazon (MIME blazon) of the file.

Patently, all that information could be stored in a dictionary, but that wouldn't exist the best arroyo in Swift. To do it ameliorate, permit'south create a struct which we'll call FileInfo. Open up the RestManager.swift file in the starter Xcode project, and go to the terminate of it. You will detect the post-obit empty extension:

This is where nosotros'll add about all new code regarding the file uploading characteristic. Inside this extension, add the following structure:

The four properties will keep the information described earlier. Equally y'all will see later, if whatever of the higher up properties is cipher the file won't be added to the HTTP body for submission to the server.

We can make the initialization of a FileInfo object more friendly if nosotros add the following custom initializer:

With this initializer, it won't be necessary to provide the bodily file contents when creating a FileInfo object. Specifying the URL of the file will exist plenty. File contents volition be read in the in a higher place initializer.

Creating The Boundary

Having a solution on our hands virtually how to represent files, allow's create a method which will be responsible of creating the boundary string. Remember that a purlieus must be unique and definitely not an ordinary string that could be potentially establish in the bodily data that will be uploaded. Equally I said in the beginning of the post, even though boundaries outset with 2 dashes ("–"), they usually have several more than dashes following and a random alphanumeric string at the end. That's non mandatory, only it's the logic nosotros will follow here.

Right afterwards the FileInfo struct, define the following private method:

I will show you two different ways to generate the random boundary string.

Using A UUID Cord

The fastest style to get a random string is to generate a UUID value:

The to a higher place will generate something similar to this:

Allow's get rid of the dashes in that cord, and allow's convert all letters to lowercase:

The original UUID will now look like this:

Let's construct the boundary string. It volition be a concatenation of 20 dashes at the start and the transformed UUID value:

If y'all like exaggerating, add the current timestamp to the end besides:

A purlieus cord created with the higher up will expect like:

Well, that looks quite unique and random, no?

Hither'due south the implementation of the unabridged method:

Using Random Characters

Equally an alternative to the above nosotros can create a mechanism which will pick random characters from a collection of available characters, and using them to grade a cord which will be appended to the boundary string. The collection of available characters will be parted by all letters ranging from upper cased "A" to "Z", lower cased "a" to "z", and all digits from "0" to "9".

We won't really demand to hard-code annihilation, equally we can programmatically construct everything. We will be based on the ASCII tabular array for that.

We'll start past specifying the range of the lower cased characters ("a" to "z") in the ASCII table as shown below:

The higher up is equivalent to this:

where 97 is the position of the "a" grapheme and "122" is the position of the "z" graphic symbol in the ASCII table.

However, the second line of code requires from us to search for an ASCII table online and and then locate the position of the characters we are interested in into the tabular array. Okay, information technology's easy, but it's definitely not the recommended way, since we tin become the values we want by using the UInt8(ascii:) initializer. And that's we do in the first place.

Similarly, we get the ranges of the upper cased A-Z and of the digits:

Now, let'due south join all these ranges into a collection, or in other words a sequence of ranges (closed ranges more specially) with aim to get the actual characters afterwards:

If we print the value of the sequenceOfRanges to the panel at runtime we'll get this:

Even though it's non obvious unless someone looks upwardly for it, the above can be easily converted into a Cord value:

Information struct provides several initializers for creating a data object and at that place is 1 amid them that accepts a sequence as an argument, exactly every bit we practice in the Data(sequenceOfRanges) expression. From that data object, we tin can create the following string which is assigned to the toString constant:

That cool! Let'due south generate a string of 20 random characters now:

At starting time nosotros initialize a cord value called randomString. So, nosotros create a loop that will exist executed twenty times. In it, we pick a random grapheme from the toString string using the randomElement() method, and nosotros generate a new Cord value (String(toString.randomElement()!)). This new String value is appended to the randomString.

Note that is rubber to force unwrap the value of the randomElement() method, as it returns nil only in cases of empty collections. Here we know that toString won't exist empty.

The post-obit is a random value of the randomString:

Finally, we tin can build the boundary string:

Here is a sample of the purlieus:

The createBoundary() method with the second implementation in 1 place:

Use the implementation you prefer the most. The second one is more than "Swifty" but information technology requires a bit of more code. At the end of the day, both approaches are going to piece of work every bit well.

An important note: I've mentioned already that the boundary string which separates the parts of a multipart body starts with two dashes ("–"). These 2 dashes are not included in the dashes of the boundary cord we generated in both approaches hither. This string will exist provided equally-is to the request as a request header along with the content type and server will attempt to locate it after the ii dashes prefix. Also, a boundary string tin exist with no dashes at all; we just add together them to minimize the possibility to find similar cord in the uploaded data. As you will encounter later, the two dashes prefix will exist manually appended whenever necessary.

Extending Data Structure

Our next steps involve the preparation of the HTTP body using whatever arbitrary data provided to the course, also as using the files data. Only before we get into that, we volition extend the Data construction and we will create the following generic method:

The purpose of this method is to let us easily append the values of the values drove to the information object that calls information technology. And as you lot'll meet, we'll be interested for String and Data types merely.

Just for clarification, we could avert implementing this method. All the same, the code that we will add to information technology would have to be repeated multiple times in different points in the RestManager class, and that definitely would non be a wise move.

And so, to continue become to the stop of the RestManager.swift file where y'all will find a Data extension:

Add together the new method's definition in it:

At commencement, we'll declare the following two local variables:

Next, we'll distinguish the type of the given values. Allow'south start with the String blazon. In this example, we'll make a loop to access all values in the values parameter collection:

In each repetition we will convert the string value into a Data object and nosotros will append it to the local newData variable. If for some reason the string value cannot be converted into a Data object, we'll prepare the status flag to imitation and we'll suspension the loop.

Nosotros will follow a quite similar approach in case of Data input values. Of course, in that location is no need to initialize any new Information object or brand a conversion of any blazon. We are appending one data value to another:

Lastly, allow's indicate that we don't intendance about any other type of values:

Next, we'll bank check the condition value. If it's true, so nosotros can suspend the newData local variable to the self object (the Data object that is used to call this method).

At the cease, nosotros should not forget to return the status equally the outcome of the method:

Here's the entire implementation. We are going to put information technology in action starting from the adjacent part.

Creating the HTTP Body

In the current implementation of RestManager there is a method named getHttpBody(). Its purpose is to gear up the HTTP trunk with the data that will exist posted to the server. Although this method works great in whatsoever other instance, unfortunately information technology's not of much help in case of file uploading. At that place is the boundary string we have to take into account, every bit well every bit the special headers and formatting required when using the "multipart/grade-data" content type. To serve our new needs, we'll implement a similarly named method which will exist accepting the boundary string as an argument (also known equally method overloading).

In the new extension of the RestManager form, correct beneath the createBoundary method, add the following:

Continue in mind that the HTTP trunk must be a Data value, then nosotros are initializing such a value in this method, and this is likewise what the method returns. In this method we'll deal with any information that should exist posted to the server except for files. That's the information that would be normally submitted if there were no files to upload at the same time, and information technology'south kept in the httpBodyParameters holding (as a reminder, httpBodyParameters is a belongings in the RestManager class and information technology'due south of RestEntity type, a custom structure – find it in RestManager and read more in the previous tutorial about it).

httpBodyParameters has a method called allValues() and returns all data as a dictionary (a [String: String] lexicon). We'll use it to access all values that should be sent to the server and append them to the body variable. Right afterwards the var body = Data() line add the following:

A pocket-sized finish here now as we take to discuss what exactly nosotros'll be appending to the body. Let's see over again part of the instance presented in the commencement of this mail service:

In this case the data is the username and the password. The following apply to each piece of data:

  • At first there is the purlieus string, and right after that a line break. In HTTP headers, a line pause is marked with "\r\n" (railroad vehicle return and new line graphic symbol), not just the "\n" that nosotros are generally used to. Programmatically, this could be written similar: "--\(purlieus)\r\north" (see the ii dashes before the purlieus cord).
  • Side by side, in that location is the "Content-Disposition" header with the name attribute only in information technology. Header is followed by a line break two times. We could write this like so: "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".
  • Lastly, information technology's the actual value followed by a line pause. That'south easy: "\(value)\r\north".

Nosotros volition add together the code that represents each step described above into an assortment:

Nosotros will use for first time the append(values:) custom method we implemented in the previous step in guild to convert these strings into Information objects and append them to the trunk variable:

And that's the last thing we had to exercise in this method. Let's run across information technology birthday now:

We'll use the results of this method in a while. For now, nosotros accept to add the files data to the HTTP body every bit well.

Adding Files To HTTP Torso

One could say that the getHttpBody(withBoundary:) method nosotros just implemented along with the new 1 we volition implement here consist of the most important part of the overall piece of work we have to do in social club to make file uploading possible. And that would exist pretty much true, every bit we've congenital all the helper methods we need and now we are dealing with the core functionality.

So, standing on building the HTTP body, let's define the following new method:

Let'due south talk get-go about the parameters. The first 1 is a drove of FileInfo objects, and it contains the data for all files that are most to be uploaded. The 2nd parameter value is the data object that represents the HTTP body. Whatsoever changes that will exist made to that object within this method will be reflected out of it as well because it'south marked with the inout keyword. The last parameter is the purlieus string, equally we necessarily need information technology to separate data parts.

You might be wondering why this method returns an optional array of String values. Well, in instance in that location are files whose data cannot be added to the HTTP torso, and so we'll keep their names into an array, which in turn the method will return. In normal conditions this method should return nil, meaning that information from all files was successfully appended to the HTTP torso information.

Let's outset adding some code, with the first 1 being the post-obit local variables:

condition will indicate whether all pieces of data for each single file in the files collection were successfully combined in ane Data object, which can be so appended to the trunk inout parameter. If status is false, we'll be appending the name of the matching file to the failedFilenames array.

Let'southward start a loop at present:

The starting time matter we have to do is to make sure that all properties of each file object have actual values so we can proceed:

Side by side, we will set the initial value of the status flag on each repetition of the loop to false, and nosotros'll initialize a new Data object.

At present, allow'due south see once again the example presented in the beginning of the tutorial then nosotros understand what we take to exercise:

Going step by step through the lines that describe a file office:

  • At first at that place is the purlieus with the line break at the end. We already know how to write that in code.
  • Next, we have the "Content-Disposition" header. The addition here (comparison to the header in the previous function) is the new filename attribute which contains the actual file proper noun. In code such a header is written similar this: "Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(filename)\"\r\northward".
  • Right after we have the content type of the file. See all the available MIME Media Types. In code this is like then: "Content-Type: \(mimetype)\r\n\r\due north".

Permit's make a intermission hither and let'southward suspend all the above to an array:

Let's catechumen all strings in that array into Data objects and append them to the information variable:

Allow's continue where we had stopped from. The next item in a file part is the bodily file contents. Think that file contents are represented by the fileContents belongings in a FileInfo object, which is a Data object. And then far nosotros were dealing with strings just. File contents must exist appended to the data variable too:

Remember that suspend(values:) method expects for an assortment of values, so information technology'due south necessary to include content into the array'south opening and closing brackets in a higher place.

Lastly, notice in the above example that in that location is an empty line correct after the file contents which should be added to the data likewise:

These iii conditions we wrote must be embedded into each other. If all of them are true, and so all data pieces for the current file were successfully added to the data object, and we'll betoken that past making the status true:

Run across that nosotros used the custom suspend(values:) custom method three times in a row hither. I hope you lot agree that its implementation was meaningful since we utilise it again and again.

Adjacent, permit'southward cheque the status value for each file. While still beingness on the loop:

If condition is true, we append the data variable to the body which represents the HTTP body. If not, and so we initialize the failedFilenames array in case it's not initialized already, and we keep the name of the current file in it.

One last thing remaining, to return the failedFilenames from the method:

Our new method should at present await like this:

Closing The HTTP Trunk

Now that we created methods which build the HTTP body by appending any mail service information and file data, we must create one more which will shut the body. Remember that in "multipart/form-data" the HTTP body closing is marked by the boundary string and 2 dashes every bit a suffix to it:

As you can guess, doing and so doesn't require much of piece of work every bit all information technology takes is this:

For one more than time here the trunk parameter is marked as inout, so the data argument will exist passed by reference and the changes fabricated to it within this method will become visible to the caller likewise. As well that, notice the line breaks before and after the closing string which ensure that the closing purlieus volition be the only content in the line.

Information technology's actually of import not to forget to call this method and indicate the end of parts in the multipart HTTP body.

Uploading Files

It's about time to put everything together and make file uploading possible. The method we'll write here will be public, so you lot tin go and add it to the tiptop of the class along with other two public methods existing already. Here is its definition:

In accordance to what nosotros did to the other two existing public methods, nosotros are going to perform all deportment in this method asynchronously. We won't run anything on the main thread since file uploading could take pregnant amount of time and we don't want apps to show frozen. In code that ways:

With userInitiated value in the quality of service parameter nosotros give our job a relatively high priority in execution. Note that we marking self as weak in the closure since the RestManager example used to perform the file uploading can potentially get nil, and that practically ways that cocky is from at present on an optional. This introduces a couple of new needs equally yous will see next.

The kickoff bodily action we accept to take is to add whatsoever URL query parameters specified in the urlQueryParameters property to the URL. This will happen by calling the addURLQueryParameters(toURL:) method which we implemented in the previous tutorial:

Next, let'due south call the createBoundary() method we implemented today and let's create the purlieus string:

Notice that since cocky is used as an optional, boundary becomes an optional value too, regardless of the fact that createBoundary() does not render an optional. And then, in example there's no purlieus string to go along, nosotros phone call the completion handler passing the mistake shown in a higher place and nosotros return from the method. This custom fault doesn't exist nonetheless in the form, we'll add it in a while.

Let's go going, and in the next pace let's add the "multipart/form-information" along with the purlieus string to the collection of the request headers:

To refresh your retention, requestHttpHeaders is a RestEntity property which keeps all HTTP request headers as key-value pairs. Information technology's of import to highlight that since we specify the content type header here, there is no need to provide a content blazon header manually while preparing the request. Non only information technology'due south redundant, it's also dangerous as it could create conflicts and brand the server reject the request.

Adjacent, let's start preparing the HTTP trunk. We'll start by calling the getHttpBody(withBoundary:) method:

Once again, since self is an optional, body might be aught in case cocky is nil. And so, in that case we call the completion handler with another custom error and nosotros return from the method.

Fourth dimension to add the files to be uploaded to the HTTP body. Notice in the adjacent line that we pass the body variable with the "&" symbol as that's an inout parameter value:

failedFilenames is either zip if all files are successfully added to the HTTP body, or it contains the names of those files that failed to exist appended to the body.

We should not forget to close the HTTP trunk properly:

We are ready now to create the URL request:

The method we use hither is already implemented in the RestManager class and we discussed about it in the previous tutorial. Notice that we pass the URL with any potential query items (targetURL) and the HTTP body as arguments.

Finally, we'll create a new URLSession and an upload task to make the request. Upon completion, we'll call the completion handler and nosotros'll laissez passer a Results object with data regarding the results of the request, and the failedFiles assortment.

The upload method is now gear up:

There is 1 final thing to do before we test out everything. To add together the ii new custom errors to the CustomError enum. Find it in the RestManager class and update it as shown adjacent:

Update its extension right below appropriately with the description of the messages:

That'southward it! Time to upload files!

Testing File Uploading

The time to test file uploading has finally come. Switch to the ViewController.swift file and add together the post-obit method definition:

For starters, we are going to upload a unmarried file only, and hither we will prepare the FileInfo object that will contain its data.

Before we proceed, let me remind you that in the starter Xcode project y'all downloaded there are 3 files for testing: "sampleText.txt", "samplePDF.txt" and "sampleImage.pdf". We'll use the "sampleText.txt" here, but feel costless to alter and use any other file y'all desire. Sample files exist in the application's bundle only for making the example every bit simple as possible, but in existent apps the you'll near ever fetch them from the documents directory.

And so, allow's start by creating a FileInfo object:

See that we are using the custom initializer we created in the FileInfo structure here. Nevertheless, in case you don't want to initialize a FileInfo object that way and you prefer to manually set all values including the files contents, here'south your culling:

Notation: Server is implemented in a way that requires the proper name attribute in every part of the multipart trunk to have the "uploadedFile" value. Therefore, that's the value that we'll be setting in the name belongings of each FileInfo object we create here.

The URL where we'll make the request to upload the file is: http://localhost:3000/upload. We will laissez passer a URL object along with an array that will contain the fileInfo object every bit arguments to a new method (we'll implement it right next):

upload(files:toURL:) is a small method responsible for making the asking as you lot tin see side by side. We could have put that code in the uploadSingleFile() method, but we'll employ information technology once again in a while when nosotros'll upload multiple files. So, we'd improve avert repeating code.

In the completion handler we don't practise annihilation item. Nosotros only print the HTTP status code, we brandish whatsoever potential errors, and the server'due south response afterwards nosotros catechumen it from JSON to a dictionary object. Of grade, we also print the list of failed to be uploaded files (in instance at that place is whatsoever).

In the viewDidLoad() method call the uploadSingleFile():

Run the app now and look at both in Xcode console and in the concluding where the server'south output is printed. If you followed everything step by step up until here, you should become this in Xcode:

At the same time, in terminal you should have the details of the uploaded file:

I wanted to make the small demo server and the file uploading process behave every bit much naturally every bit possible, so files sent to this server implementation are actually… being uploaded! In Finder, go to the Server directory that you downloaded in the starter package and and then into the subdirectory chosen "uploads". The uploaded file is in that location which proves that file uploading is actually working!

Let's make our testing more interesting by also sending additional information along with the asking. Correct after the initialization of the FileInfo object in the uploadSingleFile() method add the following two lines:

Run the app again. In the concluding you should see the boosted uploaded data every bit well:

Allow'southward upload multiple files at present. Nosotros'll exercise that by creating a new method similar to the previous one, with the difference being that instead of initializing one FileInfo object, nosotros'll initialize 3 of them so we can upload all sample files we have. Here it is:

At the stop we call once again the upload(files:toURL:) method which volition trigger the actual upload request. Notice that the upload endpoint is different this time ("multiupload"). To run across it working, don't forget to call information technology in the viewDidLoad():

This time you should run into the names of the uploaded files in terminal:

Note that the current server implementation supports up to 10 simultaneous files to be uploaded. Of course you are free to change that limit co-ordinate to your preference.

Summary

Starting in the previous tutorial where nosotros created the commencement version of the RestManager class and continuing in this i where nosotros added the file uploading feature, nosotros accept managed to build a small and lightweight class capable of covering our needs in making spider web requests. "Multipart/form-data" content type and the way HTTP trunk is congenital can exist sometimes confusing, just if you break things downwards then everything gets easy. I hope what I shared with you here today to be of some value, and I wish y'all are enjoying RESTful services even more now. You are e'er welcome to add more features or adapt the electric current implementation according to your needs. Meet you lot next time!

For reference, you can download the total project on GitHub.

ortizplas1979.blogspot.com

Source: https://www.appcoda.com/restful-api-tutorial-how-to-upload-files-to-server/

0 Response to "Rest Api Load Testing File Upload Folder"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel