B.13. Form Upload File

Pada bagian ini kita akan belajar bagaimana cara meng-handle upload file yang dilakukan via form submit di sisi front-end. Pada beberapa bagian, caranya mirip seperti pada chapter sebelumnya, perbedaannya kali ini handler berisi proses untuk handling file yang di-upload. Proses yang dimaksud adalah memproses payload file dari front-end untuk kemudian disimpan ke path/folder.

B.13.1. Struktur Folder Proyek

Sebelum mulai masuk ke bagian koding, siapkan terlebih dahulu file dan folder dengan struktur seperti gambar berikut.

Dasar Pemrograman Golang - Folder Structure

Program sederhana yang akan dibuat memiliki satu form dengan 2 inputan yaitu alias dan file. Di back-end, nantinya data file disimpan ke folder files, dengan default nama file sesuai nama file aslinya. Kecuali ketika user mengisi inputan alias, maka nama tersebut yang akan digunakan sebagai nama file tersimpan.

B.13.2. Front End

Di bagian front end, isi file view.html dengan kode berikut. Template file ini nantinya yang dimunculkan sebagai landing page.

<!DOCTYPE html>
<html>
    <head>
        <title>Input Message</title>
    </head>
    <body>
        <form method="post" action="/process" enctype="multipart/form-data">
            <label>The file :</label>
            <input type="file" name="file" required /><br />

            <label>Rename to :</label>
            <input type="text" name="alias" /><br />

            <button type="submmit">Submit</button>
        </form>
    </body>
</html>

Perlu diperhatikan, pada tag <form> perlu ditambahkan atribut enctype="multipart/form-data", agar HTTP request mendukung operasi upload file.

B.13.3. Back-End

Di layer back-end ada cukup banyak package yang perlu di-import, seperti os, io, path/filepath, dan lainnya. Packages tersebut kita perlukan untuk handling file upload.

Pada fungsi main() siapkan 2 buah route handler, satu untuk landing page dan satunya lagi digunakan ketika proses upload selesai (sama seperti pada chapter sebelumnya).

package main

import "net/http"
import "fmt"
import "os"
import "io"
import "path/filepath"
import "html/template"

func main() {
    http.HandleFunc("/", routeIndexGet)
    http.HandleFunc("/process", routeSubmitPost)

    fmt.Println("server started at localhost:9000")
    http.ListenAndServe(":9000", nil)
}

Handler route / isinya proses untuk menampilkan landing page (file view.html). Method yang diperbolehkan mengakses rute ini hanya GET.

func routeIndexGet(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        http.Error(w, "", http.StatusBadRequest)
        return
    }

    var tmpl = template.Must(template.ParseFiles("view.html"))
    var err = tmpl.Execute(w, nil)

    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

Selanjutnya siapkan handler untuk rute /proccess, yaitu fungsi routeSubmitPost. Gunakan statement r.ParseMultipartForm(1024) untuk parsing form data yang dikirim.

func routeSubmitPost(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Error(w, "", http.StatusBadRequest)
        return
    }

    if err := r.ParseMultipartForm(1024); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // ...
}

Method ParseMultipartForm() digunakan untuk mem-parsing form data yang ada data file-nya. Argumen 1024 pada method tersebut adalah maxMemory. Pemanggilan method tersebut membuat file yang terupload disimpan sementara pada memory dengan alokasi sesuai maxMemory. Jika ternyata kapasitas yang sudah dialokasikan tersebut tidak cukup, maka file akan disimpan dalam temporary file.

Masih dalam fungsi routeSubmitPost(), tambahkan kode untuk mengambil data alias dan file.

alias := r.FormValue("alias")

uploadedFile, handler, err := r.FormFile("file")
if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
}
defer uploadedFile.Close()

dir, err := os.Getwd()
if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
}

Statement r.FormFile("file") digunakan untuk mengambil file yg di upload, dan mengembalikan 3 objek:

  • Objek bertipe multipart.File (yang merupakan turunan dari *os.File)
  • Informasi header file (bertipe *multipart.FileHeader)
  • Dan error jika ada

Tahap selanjutnya adalah menambahkan kode membuat file baru, yang nantinya file ini akan diisi dengan isi dari file yang ter-upload. Jika inputan alias di-isi, maka nama nilai inputan tersebut dijadikan sebagai nama file.

filename := handler.Filename
if alias != "" {
    filename = fmt.Sprintf("%s%s", alias, filepath.Ext(handler.Filename))
}

fileLocation := filepath.Join(dir, "files", filename)
targetFile, err := os.OpenFile(fileLocation, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
}
defer targetFile.Close()

if _, err := io.Copy(targetFile, uploadedFile); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
}

w.Write([]byte("done"))

Fungsi filepath.Ext() digunakan untuk mengambil ekstensi dari sebuah file. Pada kode di atas, handler.Filename yang berisi nama file terupload diambil ekstensinya, lalu digabung dengan alias yang sudah terisi.

Fungsi filepath.Join() berguna untuk pembentukan path.

Fungsi os.OpenFile() digunakan untuk membuka file. Fungsi ini membutuhkan 3 buah argument parameter dalam pemanggilannya:

  • Parameter pertama merupakan path atau lokasi dari file yang ingin di buka.
  • Parameter kedua adalah flag mode, apakah read only, write only, atau keduanya, atau lainnya.
    • os.O_WRONLY|os.O_CREATE maknanya, file yang dibuka hanya akan bisa di tulis saja (write only konsantanya adalah os.O_WRONLY), dan file tersebut akan dibuat jika belum ada (konstantanya os.O_CREATE).
  • Sedangkan parameter terakhir adalah permission dari file, yang digunakan dalam pembuatan file itu sendiri.

Fungsi io.Copy() mengisi konten file parameter pertama (targetFile) dengan isi parameter kedua (uploadedFile). File kosong yang telah kita buat tadi akan diisi dengan data file yang tersimpan di memory.

Nantinya pada salah satu pembahasan pada chapter B.16. AJAX Multiple File Upload akan dijelaskan cara handling file upload dengan metode yang lebih efektif dan hemat memori, yaitu menggunakan MultipartReader.

B.13.4. Testing

Jalankan program, test hasilnya lewat browser.

Dasar Pemrograman Golang - Test upload