Initiating upload

While creation extension here is not the core protocol, but extension, it is still a good starting point to understand how the client can initiate the upload. The creation extension allows the client to create a new upload resource on the server.

If we follow tus protocol, the client can initiate the upload by calling the POST method to /api/v1/files endpoint:

POST /api/v1files HTTP/1.1
Content-Length: 0
Upload-Length: 100
Tus-Resumable: 1.0.0
Upload-Metadata: filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,content-type YXBwbGljYXRpb24vcGRm,checksum c2hhMjU2OjEyMzQ1Njc4OTA=

And server should response with the following:

HTTP/1.1 201 Created
Location: https://tus.example.org/files/24e533e02ec3bc40c387f1a0e460e216
Tus-Resumable: 1.0.0

For our initial implementation, we will ignore the Tus-Resumable header for both request and response. We will get back to the Tus-Resumable header later when we implement the middleware to check the tus protocol version.

Let’s stop for a moment and think about the request and response. If you ever wondering, why the metadata is sent as a header instead of json body? Don’t you think that it is easier to parse the metadata if it is sent as a json body (especially if you are only familiar with json)? As matter of fact, that’s how Google Cloud Storage implement their initiation upload API. They accept the metadata as a json body. You may refer to the Google Cloud Storage documentation to see how they implement the initiation upload API.

On the other hand, tus protocol allows creation with request body set to empty (described by: Content-Length: 0). In this case, the client is not sending the file content yet, it is only sending the metadata of the file. However, tus protocol also has another extension called creation-with-upload that allows the client to create a new upload resource and upload the first chunk in a single request. If you read more here, client can send the file content in the same request as the initialization request to reduce the number of requests needed to upload the file. In this case, body request will always be used for the file content. That’s why the metadata is sent as a header, unlike GCS implementation.

So nothing is right or wrong here. It is up to you to decide how you want to implement the initiation upload API. In this lesson, we will follow the tus protocol and implement the initiation upload API as described above. But, we will not implement the creation-with-upload extension. I will leave it to you to implement it as an exercise.

Defining controller class

Let’s start the code.

Here, I want to define a controller struct that will host the API handler. Other than this, we will possibly keep other business logic here for simplicity.

type Controller struct {
    store      Storage	
}

func NewController(store Storage) *Controller {
    return &Controller{store: store}
}

If you have implemeted your own storage implementation, you can pass it to the controller. The controller will then use the storage to store and get the metadata of the file.

Defining the handler

Let’s start by registering the route for the upload initiation. We will use gorilla/mux to handle the routing.

Now, let’s define the handler for the upload initiation:

func (c *Controller) CreateUpload() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
       // implement this later 
    }
}

Registering routes

Once we have the controller, let’s register the route to the router:

mux := mux.NewRouter()
apiRouter := mux.PathPrefix("/api").Subrouter()
apiV1Router := apiRouter.PathPrefix("/v3").Subrouter()

controller := NewController(InMemoryStore())

apiV3Router.HandleFunc("/files", controller.CreateUpload()).Methods(http.MethodPost)

Now we have the route registered. On the next lesson, let’s run through each specification defined by tus protocol for the upload initiation.