首頁  >  文章  >  後端開發  >  使用 HTMX 和 Golang 上傳文件

使用 HTMX 和 Golang 上傳文件

WBOY
WBOY原創
2024-08-20 06:32:41619瀏覽

當然你已經聽說過 HTMX 的強大之處(你還沒聽說過嗎?好吧,幸好你在這裡?)

今天,我們將結合 HTMX 的簡單性和 Golang 的強大功能來將檔案上傳到我們的伺服器。是的,我們將使用 HTMX 和 Go 來建立另一個令人興奮的 Web 功能。

順便說一句,如果您確實想要一本關於使用HTMX 建立全端應用程式的基於專案的實用指南,請查看我的HTMX + Go:使用Golang 和HTMX 建立全端應用程式課程[含折扣]。

那麼,我們開始吧。

設定 Go 項目

第一步是設定一個簡單的 Go 項目。我們可以透過建立一個資料夾,進入其中並使用以下命令將其初始化為 Go 專案來做到這一點:

mkdir go-htmx-file-uploads
cd go-htmx-file-uploads
go mod init go-htmx-file-uploads

初始化專案後,現在讓我們安裝專案中需要的一些依賴項。

這將是一個簡單的伺服器,其中包含帶有我們的上傳表單的單一頁面以及用於上傳檔案的端點。

對於路由,我們將使用 Gorilla Mux 路由庫,但請隨意使用您選擇的任何路由解決方案。我們還將使用 Google 的 Go 的 UUID 庫在上傳文件時為文件產生隨機名稱。這是個人喜好,因為您可以透過不同的方式產生檔案名稱。

使用以下指令安裝這兩個:

大猩猩 Mux

go get -u github.com/gorilla/mux 

Google UUID

go get github.com/google/uuid

安裝這兩個後,我們的專案就完全建立起來了,我們可以進入下一步。

創建我們的模板

我們將為這個小專案建立兩個 HTML 範本。

第一個範本將是一個 HTML 片段,它只需要我們可以從伺服器傳送到客戶端的字串訊息片段。

此片段將獲取此訊息片段並循環遍歷它以建立要傳回給客戶端的 HTML 清單(還記得 HTMX 如何與超媒體 API 配合使用,很酷吧?)。

那麼,讓我們先創建它。

在 Go 專案的根目錄下,首先建立一個 templates 資料夾,我們將儲存所有範本。

接下來,在 templates 資料夾中建立一個檔案 messages.html 並在其中加入以下程式碼:

{{define "messages"}}
<ul>
    {{range .}}
        <li>{{ . }}</li>
    {{end}}
</ul>
{{end}}

這定義了一個訊息模板,並循環傳入的字串訊息片段以形成 HTML 清單。

對於我們的下一個模板,我們將建立文件上傳頁面本身。

在 templates 資料夾中,建立一個新檔案 upload.html 並貼上以下程式碼:

{{define "upload"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://unpkg.com/htmx.org@1.9.12"></script>
    <title>Upload File</title>
</head>
<body>

    <div class="row">
        <div class="col-md-6 p-5 mt-5">
            <h4>Upload File</h4>
            <form  class="form">
                <div id="messages"></div>
                <div class="mb-3">
                    <label for="avatarInput" class="form-label">Select Image</label>
                    <input type="file" class="form-control" id="avatarInput" name="avatar" required>
                </div>
                <button 
                    hx-post="/upload" 
                    hx-encoding="multipart/form-data" 
                    hx-target="#messages" 
                    type="submit" class="btn btn-primary">Upload</button>
            </form>
        </div>
    </div>



</body>
</html>
{{end}}

完美!

現在我們來看看這個檔案中的程式碼。

首先,我們定義了名為 upload 的模板,這是我們稍後在路由處理程序中用來引用它的名稱。

然後我們在 head 部分有一些樣板 HTML 程式碼,但我在這裡包含了兩個重要的函式庫(嗯,只有一個非常重要,另一個僅用於 CSS 共鳴)。

HTMX 庫已包含在 <script> 中標籤引入 HTMX,只需庫,不需要任何依賴項。 </script>

然後我還引入了 Bootstrap CSS 庫,這只是為了給我們的頁面元素一些漂亮的樣式。此演示不是強制性的。

在頁面本身中,我們有一個用於上傳的表單。讓我們分解一下

中的內容:標籤。

首先我們有一個

; id 為 messages ,這是我們將載入所有以 HTML 形式出現的伺服器訊息的容器。記住訊息模板,是的,這就是訊息清單所在的位置。

之後,我們將表單輸入元素設定為文件,以確保它顯示文件上傳小工具。我們已為其指定名稱 avatar 以在後端引用它,但您可以為其指定任何名稱。我給它頭像是因為我用它來上傳個人資料圖片。

最後,我們有了經過 HTMX 增強的按鈕。我在下面再次展示了它,以便我們可以瀏覽一下

<button 
                    hx-post="/upload" 
                    hx-encoding="multipart/form-data" 
                    hx-target="#messages" 
                    type="submit" class="btn btn-primary">Upload</button>

首先,我新增了 hx-post="/upload" ,這告訴它將表單提交到我們很快就會建立的 /upload 端點,並處理檔案上傳。

接下來是 hx-encoding="multipart/form-data",這是使用 HTMX 上傳檔案所必需的,以便讓伺服器知道您正在隨請求發送檔案。

然後我們有 hx-target="#messages" 告訴按鈕將來自伺服器的任何回應插入到

中帶有訊息 ID。

這三個定義了將檔案上傳到後端的設定。

以下是我們頁面的預覽:

File Uploads with HTMX and Golang

Processing the File Upload

Now that we have our templates, it’s time to write the code that will display our upload page and also handle our file uploads.

To begin, at the root of the Go project, create a uploads folder. This is the folder where all our uploaded files will be stored.

With that in place, let’s write our main file.

Create the file main.go at the root of your project and add the following code:

package main

import (
    "html/template"
    "log"
    "net/http"
    "io"
    "os"
    "path/filepath"
    "github.com/google/uuid"
    "github.com/gorilla/mux"
)

var tmpl *template.Template

func init(){
    tmpl, _ = template.ParseGlob("templates/*.html")
}

func main() {


    router := mux.NewRouter()

    router.HandleFunc("/", homeHandler).Methods("GET")

    router.HandleFunc("/upload", UploadHandler).Methods("POST")

    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", router))
}

func homeHandler(w http.ResponseWriter, r *http.Request) {

    tmpl.ExecuteTemplate(w, "upload", nil)

}

func UploadHandler(w http.ResponseWriter, r *http.Request) {


        // Initialize error messages slice
        var serverMessages []string

        // Parse the multipart form, 10 MB max upload size
        r.ParseMultipartForm(10 << 20)

        // Retrieve the file from form data
        file, handler, err := r.FormFile("avatar")
        if err != nil {
            if err == http.ErrMissingFile {
                serverMessages = append(serverMessages, "No file submitted")
            } else {
                serverMessages = append(serverMessages, "Error retrieving the file")
            }

            if len(serverMessages) > 0 {
                tmpl.ExecuteTemplate(w, "messages", serverMessages)
                return
            }

        }
        defer file.Close()

        // Generate a unique filename to prevent overwriting and conflicts
        uuid, err := uuid.NewRandom()
        if err != nil {
            serverMessages = append(serverMessages, "Error generating unique identifier")
            tmpl.ExecuteTemplate(w, "messages", serverMessages)

            return
        }
        filename := uuid.String() + filepath.Ext(handler.Filename) // Append the file extension

        // Create the full path for saving the file
        filePath := filepath.Join("uploads", filename)

        // Save the file to the server
        dst, err := os.Create(filePath)
        if err != nil {
            serverMessages = append(serverMessages, "Error saving the file")
            tmpl.ExecuteTemplate(w, "messages", serverMessages)

            return
        }
        defer dst.Close()
        if _, err = io.Copy(dst, file); err != nil {
            serverMessages = append(serverMessages, "Error saving the file")
            tmpl.ExecuteTemplate(w, "messages", serverMessages)
            return
        }


        serverMessages = append(serverMessages, "File Successfully Saved")
        tmpl.ExecuteTemplate(w, "messages", serverMessages)


}

Yope, that’s a bunch of code. Don’t worry, we’ll go through it all step by step to figure out what this is all doing.

First we define our package main and import a bunch of libraries we will be making use of. These imports include the Gorilla mux router and the Google UUID library that we installed earlier.

After that, I create a global tmpl variable to hold all the HTML templates in the project and in the init() function, the templates are all loaded from the templates folder.

The main() Function

Now to the main() function. Here, we have initlialized the Gorilla Mux router and set up two routes.

The GET / base route which will be handled by a homeHandler function and displays our upload form, and the POST /upload route that will be handled by UploadHandler and handles the upload itself.

Finally, we print out a message to indicate that our server is running, and run the server on port 8080.

The Handler Functions

First we have homeHandler . This is the function that handles our base route, and it simply calls ExecuteTemplate on the tmpl variable with the name we gave to our template

tmpl.ExecuteTemplate(w, "upload", nil)

This call is enough to simply render our upload page to the screen when we visit the base route.

After that is the UploadHandler function. This is where the real magic happens, so let’s walk through the function.

First, we create a slice of strings called serverMessages to hold any message we want to send back to the client.

After that, we call ParseMultipartForm on the request pointer to limit the size of uploaded files to within 20MB.

r.ParseMultipartForm(10 << 20)

Next, we get a hold on our file by referencing the name of the file field with FormFile on the request pointer.

With our reference to the file, we check if there is actually a file, and if not, we return a message saying that no file was submitted or an error was encountered when trying to retrieve the file to account for other errors.

file, handler, err := r.FormFile("avatar")
        if err != nil {
            if err == http.ErrMissingFile {
                serverMessages = append(serverMessages, "No file submitted")
            } else {
                serverMessages = append(serverMessages, "Error retrieving the file")
            }

            if len(serverMessages) > 0 {
                tmpl.ExecuteTemplate(w, "messages", serverMessages)
                return
            }

        }

At this point, if our messages slice is not empty, we return the messages to the client and exit the function.

If a file is found, we keep the file open and move to generating a new name for it with the UUID library and also handle the errors in that process accordingly.

We build a new file name with the generated string and the file extension and set it’s path to the uploads folder.

    uuid, err := uuid.NewRandom()
        if err != nil {
            serverMessages = append(serverMessages, "Error generating unique identifier")
            tmpl.ExecuteTemplate(w, "messages", serverMessages)

            return
        }
        filename := uuid.String() + filepath.Ext(handler.Filename) 

        // Create the full path for saving the file
        filePath := filepath.Join("uploads", filename)

Once the new file path is constructed, we then use the os library to create the file path

After that, we use the io library to move the file from it’s temporary location to the new location and also handle errors accordingly.

    dst, err := os.Create(filePath)
        if err != nil {
            serverMessages = append(serverMessages, "Error saving the file")
            tmpl.ExecuteTemplate(w, "messages", serverMessages)

            return
        }
        defer dst.Close()
        if _, err = io.Copy(dst, file); err != nil {
            serverMessages = append(serverMessages, "Error saving the file")
            tmpl.ExecuteTemplate(w, "messages", serverMessages)
            return
        }

If we get no errors from the file saving process, we then return a successful message to the client using our messages template as we have done with previous messages.

serverMessages = append(serverMessages, "File Successfully Saved")
tmpl.ExecuteTemplate(w, "messages", serverMessages)

And that’s everything.

Now let’s take this code for a spin.

Testing the File Upload

Save the file and head over to the command line.

At the root of the project, use the command below to run our little file upload application:

go run main.go

Now go to your browser and head over to http://localhost:8080, you should see the upload screen displayed.

Try testing with no file to see the error message displayed. Then test with an actual file and also see that you get a successful message.

Check the uploads folder to confirm that the file is actually being saved there.

File Uploads with HTMX and Golang

And Wholla! You can now upload files to your Go servers using HTMX.

Conclusion

If you have enjoyed this article, and will like to learn more about building projects with HTMX, I’ll like you to check out HTMX + Go: Build Fullstack Applications with Golang and HTMX, and The Complete HTMX Course: Zero to Pro with HTMX to further expand your knowledge on building hypermedia-driven applications with HTMX.

以上是使用 HTMX 和 Golang 上傳文件的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn