首页  >  文章  >  后端开发  >  使用 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