Sending an Image as POST Request with Swift 5 to Go 1.19 Server
Tags: swift, go, tutorial, ios
Published: 2022-10-26 15:33:07

In this post we will upload an image from the user of an iOS app to a server written in Go as an asynchronous multiform POST request. This could be for uploading a profile image or detecting objects.

We will just return file size, image width and height in pixels as a basic JSON object. If you want to learn how to cast more complex JSON objects into structs, checkout [Relaying a Microservice JSON Response to the Client by Unmarshalling Go Structs](https://sebastianroy.de/relaying-a-microservice-json-response-to-the-client-by-unmarshalling-go-structs/). 

First, we need to obtain the image we intend to send. This could be done by taking a photo using the camera, selecting an image from the library, or loading an image from a URL. Loading an image from a URL is the easiest way. Then we need to create the POST request, send it and catch any error if something went wrong.

1. Backend in Go 1.19
2. Returning a JSON with file name, size, width and height
3. Frontend in Swift 5
4. Basic Concepts
5. Code Skeleton
6. Selecting an Image
7. Creating and Sending the HTTP Request
8. Conclusion
9. Links

If we can’t accept POST requests that contain an image, any request will remain unanswered. So let’s create a server to do that.

## Backend in Go 1.19
First initialise a new Go environment and install the [echo](https://echo.labstack.com/) framework.
```bash
$ mkdir ImageHandlerTutorial && cd ImageHandlerTutorial 
$ go mod init ImageHandlerTutorial 
$ go get github.com/labstack/echo/v4 
$ go get github.com/labstack/echo/v4/middleware  
```

Then create a `ImageHandlerTutorial.go` file with a basic skeleton for using [echo](https://echo.labstack.com/) and other imports for writing files or encoding JSON.
```go
package main

import (
	"bytes"
	// Formating strings
	"fmt"

	"io"
	// Multipart POST request
	"mime/multipart"
	// Opening and closing files
	"os"
	"time"

	"image"
	// Register possible image formats
	_ "image/png"
	_ "image/jpeg"

	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"

	// Echo framework
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {
	e := echo.New()

	// Avoid CORS error when testing with redocly
	e.Use(middleware.CORS())

	// API Versioning
	//...

	// POST request handling
	//...

	e.Logger.Fatal(e.Start(":1324"))
}
```

To provide some structure for the URLs, we create two groups, such that we can create resources under `127.0.0.1:1324/api/v1`. The IP will change if it is running on a real server instead of the same local machine the iOS Simulator will run on later.
 
```go
	// API Versioning
	apiGroup := e.Group("/api")
	apiv1Group := apiGroup.Group("/v1")
```

Under this group, we now create an endpoint `imageanalysis`. It will handle the POST request `127.0.0.1:1324/api/v1/imageanalysis`.

This also includes a function to handle the image file. We open the data in field `file` into the source `src` and write the file to `dst` on disk. 

The way it is written here, different uploads will overwrite the same file name `file`. This is a bit sketchy, but also doesn’t use much space. Think about deleting the file after analysis, renaming it if you need it later, and what happens when multiple users call the function at the same time.

```go
	apiv1Group.POST("/imageanalysis", func(c echo.Context) error {
		// TODO: Catch error when no file is send in POST Request
		// Source
		file, err := c.FormFile("file")
		if err != nil {
			return err
		}
		src, err := file.Open()
		if err != nil {
			return err
		}
		defer src.Close()

		// Destination
		dst, err := os.Create(file.Filename)
		if err != nil {
			return err
		}
		defer dst.Close()

		// Copy
		if _, err = io.Copy(dst, src); err != nil {
			return err
		}
		// TODO: Why do i need to open the file here again, instead of just passing the reference from os.Create()?
		// TODO: Maybe don't create the file on disk at all, just send it from memory
		buf, err := os.Open(dst.Name())
		if err != nil {
			log.Fatal(err)
		}
		defer buf.Close()

		return ImageRequestHandler(c, buf)
	})


```

To finish, we start the echo server (but that part is already in the skeleton above)
```go
e.Logger.Fatal(e.Start(":1324"))
```

Save the file and to run the server open a terminal and execute.
```bash
$ go run ImageHandlerTutorial.go          
```

This should output

```bash
   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.9.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:1324
```

Go Playground doesn’t support anything with networking, so if you want to try it, you need to install [Go](https://go.dev/dl/) on your machine. You can see and download the full listing [here](https://ideone.com/kkPNWW).


## Returning a JSON with file name, size, width and height 

So for practice, let’s return a JSON with the file size in kb and image size in pixels. Here we could also send the image for analysis to another server to do object detection. We would need to perform another POST request, this time from Go to an object detection microservice, and I have written about this in [Sending Images in POST request as MultiPart Form from Go to Microservice](https://sebastianroy.de/sending-images-in-post-request-as-multipart-form-from-go-to-microservice/).

```go
// Send an image to microservice for image analysis
func ImageRequestHandler(c echo.Context, img *os.File) error {
	// TODO: Use user image instead
	log.Print(img.Name())

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	// TODO: Try to change image name here
	fw, err := writer.CreateFormFile("image", "image.jpg")

	_, err = io.Copy(fw, img)
	if err != nil {
		log.Fatal(err)
	}

	writer.Close()

	// Get filesize of image
	stat, err := img.Stat()
	log.Print("File size: ", stat.Size()/1024, " kb")

	// Get width and height in px for the image
	im, _, err := image.DecodeConfig(img)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s: %v\n", img.Name(), err)
	}
	log.Print(im.Width)

	// Form JSON response that contains name, size, height and width
	jsonStr := fmt.Sprintf(`{ "name": "%s",
				 "size": %d,
				 "width": %d,
				 "height": %d
				}`, img.Name(), stat.Size(), im.Width, im.Height)
	log.Print(jsonStr)

	c.Response().Header().Set("Content-Type", "application/json")
	//TODO: Avoid double casting []byte string?!
	c.Response().Write([]byte(string(jsonStr)))

	// TODO: Figure out how to avoid return and just return with c.Response() similar to w.Write
	return err
}
```

## Basic concepts
### Multiform/data http requests
A `POST` request is one type of _HTTP request_ like `GET` and `PUT`. There are many types of `POST` request, which we can specify in the header. There are `image/png` or `text` for example. For flexibility, however, we will use `multiformat/data`. This offers multiple data fields.

HTTP requests have a _boundary_. It tells the parser that interesting data is coming and when it sees the same boundary again that this is the end of that data batch. We will define it using a random `uuidString`. This is just a common way to generate random text to uniquely identify something and can look like `633a76d8-87a3-494c-bd1c-7192985e01f5`. 

### Asynchronous tasks with DispatchQueues
Like any type of networking activity, it takes some time to execute and might even fail. We want to avoid blocking the user interface waiting for a response. That’s why multithreading is used. This is a way to make asynchronous networking requests. There are many ways to do this and it is a complex subject. Here we will use `DispatchQueues`. This is a way to schedule tasks with the first-in-first-out principle (FIFO) and we let iOS handle thread management.
 


## Swift Code Skeleton 

We will use two functions `loadFrom` and `uploadImage` and wrap those up in a class `POSTImage`. Then we create an object `postRequest` of our new type `POSTImage` and execute.

```swift
import UIKit

class POSTImage {
	var image = UIImage()
	
	// Load an image from URL 
	func loadFromURL(URLAddress: String) {
		 
	}

	// Load an image from Library (TODO)
	func loadImageFromLibrary() {}

	// Sending the image to the server
	func uploadImage(fileName: String, image: UIImage) {
	
	}
}


let postRequest = POSTImage()
postRequest.loadFromURL(URLAddress: "https://upload.wikimedia.org/wikipedia/commons/b/b9/Panther_chameleon_%28Furcifer_pardalis%29_male_Montagne_d%E2%80%99Ambre.jpg")

```

## Selecting an Image

### Load an image from URL

So, now let’s get an image by loading it from a URL with `loadFromURL(...)` with an `URLAddress` of type `String` as argument. We specified the URL in the last line of our skeleton above already. In a real scenario, this could be using a URL from a s3 object storage. We use `guard` to catch errors when parsing the URL.

```swift
 guard let url = URL(string: URLAddress) else {
     return
 }
```

Next we will load the image. Loading an image takes some time and can fail. We would rather not block the user interface, therefore handling the task over to a separate thread with `DispatchQueue.main.async`. First, we try to obtain the `Data` from the `url` into `imageData`. Then the `imageData` will be cast into `loadedImage` which can be used for display as `UIImage`. 

Now we will upload the image. This is a provisional solution, but it works. The issue is that we loaded the image in a separate thread. We can only call `uploadImage`, once we already got the image. Therefore, our program has to wait or we need a callback function that is called after the image was loaded from URL. We will do that later, but for now, we just call `uploadImage` from the sub thread in `DispatchQueue`. 

```swift
DispatchQueue.main.async { [weak self] in
    if let imageData = try? Data(contentsOf: url) {
    if let loadedImage = UIImage(data: imageData) {
       self?.image = loadedImage
       // TODO: Remove this and use completion handler instead
       self?.uploadImage(fileName: "file", image: loadedImage)
    }
}
```

The other issue is that we haven’t implemented `uploadImage` yet, which we will do next. Here is the full listing for the function `selectImageFromURL`.

```swift
func selectImageFromURL(URLAddress: String) {
     guard let url = URL(string: URLAddress) else {
         return
     }
        
     DispatchQueue.main.async { [weak self] in
        if let imageData = try? Data(contentsOf: url) {
    		if let loadedImage = UIImage(data: imageData) {
    		   self?.image = loadedImage
      		 // TODO: Remove this and use completion handler instead
    		   self?.uploadImage(fileName: "file", image: loadedImage)
    }
}
```

## Create and send POST request 

Now we create a [URLSession](https://developer.apple.com/documentation/foundation/urlsession) named `session`. It will be sent to the `url` specified by `URL(...)`. This must be an endpoint to your server, that will handle HTTP POST requests. I have written a previous post about how to do this also with `URLSession` and `DispatchQueues` at least with JSON objects (text, no images) in [Communications from Go Backend to SwiftUI Frontend with JSON API](https://developer.apple.com/documentation/foundation/urlsession).

First, we need to define the server URL and start a [URLSession](https://developer.apple.com/documentation/foundation/urlsession) to handle networking.
```swift
let url = URL(string: "http://127.0.0.1:1324/api/v1/imageanalysis")

let session = URLSession.shared
```

Now we configure the request as `POST` request and making it a `multipart/form-data` request in the header, where we configure how the `boundary` looks like.
```swift
let boundary = UUID().uuidString
var urlRequest = URLRequest(url: url!)
urlRequest.httpMethod = "POST"
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
```

After the header we need to set the actual data. We create `data` of type `Data`. Then we append the boundary and add the parameter ”file”. The `Content-Type` of this parameter is `image/png`. And finally we have to append the actual file data `image.pngData`. Finally, we close the requests boundary.

```swift
var data = Data()
        
// Add the image data to the raw http request data
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)

let paramName = "file"
data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
data.append(image.pngData()!)
        
data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
```

Now that we defined the request, it is time to execute it. We use the `session.uploadTask` with our created `urlRequest` and `data` as arguments. Then we handle the response in the `completionHandler`. There we convert the JSON response into a Go struct.
```swift
session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
                if error == nil {
                    let jsonData = try?JSONSerialization.jsonObject(with: responseData!, options: .allowFragments)
                    if let json = jsonData as? [String: Any] {
                        print(json)
                    }
                }
	}).resume()
```

Now let’s look at the full listing of the function `uploadImage`. 
```swift
func uploadImage(fileName: String, image: UIImage) {
        let url = URL(string: "http://127.0.0.1:1324/api/v1/electrophoresis/imageanalysis")
        let boundary = UUID().uuidString

        let session = URLSession.shared

        var urlRequest = URLRequest(url: url!)
        urlRequest.httpMethod = "POST"

        urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

        var data = Data()
        
        // Add the image data to the raw http request data
        data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
        let paramName = "file"
        data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
        data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
        data.append(image.pngData()!)
        
        data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
        
        session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
                if error == nil {
                    let jsonData = try? JSONSerialization.jsonObject(with: responseData!, options: .allowFragments)
                    if let json = jsonData as? [String: Any] {
                        print(json)
                    }
                }
            }).resume()
    }
```

Finally, we get to see the result of all the hard work so far:
```swift
["size": 5451466, "name": file, "height": 1396, "width": 2872]
```

This is the analysis done by the server, shown on the client. Real world analysis would be more complex and the client would display that data on the UI instead of just printing it to the console. 

## Bonus: Some curious errors
One issue I had was that data function wasn’t found. This was because I created my own data type that was called `data`. This has overridden the native function. So give your types new names that are not used in iOS. iOS libraries prefix any new class or struct to avoid it. 

## Full Listing 
```swift
import UIKit


class POSTImage {
    var image = UIImage()
    
    func loadFrom(URLAddress: String) {
        guard let url = URL(string: URLAddress) else {
            return
        }
        
        DispatchQueue.main.async { [weak self] in
            if let imageData = try? Data(contentsOf: url) {
                if let loadedImage = UIImage(data: imageData) {
                     // Why is self optional here?
                     self?.image = loadedImage
                     self?.uploadImage(fileName: "file", image: loadedImage)
                }
            }
        }
    }
    
    func uploadImage(fileName: String, image: UIImage) {
        let url = URL(string: "http://127.0.0.1:1324/api/v1/imageanalysis")
        let boundary = UUID().uuidString

        let session = URLSession.shared

        var urlRequest = URLRequest(url: url!)
        urlRequest.httpMethod = "POST"

        urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

        var data = Data()
        
        // Add the image data to the raw http request data
        data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
        let paramName = "file"
        data.append("Content-Disposition: form-data; name=\"\(paramName)\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
        data.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
        data.append(image.pngData()!)
        
        data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
        
        session.uploadTask(with: urlRequest, from: data, completionHandler: { responseData, response, error in
                if error == nil {
                    let jsonData = try? JSONSerialization.jsonObject(with: responseData!, options: .allowFragments)
                    if let json = jsonData as? [String: Any] {
                        print(json)
                    }
                }
            }).resume()
    }
}

let postRequest = POSTImage()
postRequest.loadFrom(URLAddress: "https://upload.wikimedia.org/wikipedia/commons/b/b9/Panther_chameleon_%28Furcifer_pardalis%29_male_Montagne_d%E2%80%99Ambre.jpg")

// TODO: wait for myimageview to load
// uploadImage(fileName: "file", image: myImageView.image)

```

## Conclusion

To sum things up, we discussed the server and client side of sending images over HTTP with POST requests, doing simple analysis on the server and returning the result as JSON to the client. I hope I could cover this subject in a useful way with most of the puzzle pieces. 

## Links
[https://orjpap.github.io/swift/http/ios/urlsession/2021/04/26/Multipart-Form-Requests.html](https://orjpap.github.io/swift/http/ios/urlsession/2021/04/26/Multipart-Form-Requests.html)
[https://stackoverflow.com/questions/29623187/upload-image-with-multipart-form-data-ios-in-swift](https://stackoverflow.com/questions/29623187/upload-image-with-multipart-form-data-ios-in-swift)
[https://www.hackingwithswift.com/books/ios-swiftui/sending-and-receiving-orders-over-the-internet](https://www.hackingwithswift.com/books/ios-swiftui/sending-and-receiving-orders-over-the-internet)