如何在内存中写入文件,并通过HTTP发送,而不使用中间文件?

huangapple go评论89阅读模式
英文:

How to write to a file in-memory, and then send it via HTTP without using an intermediate file?

问题

我需要请求、处理和格式化数据到CSV,并在请求时通过Web服务发送它们。

假设请求和处理的数据在下面的data中,我使用一个中间临时文件来实现这个目标:

package main

import (
	"encoding/csv"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"

	"github.com/go-chi/chi/v5"
)

type Data struct {
	Name string
	Age  int
}

func main() {
	data := []Data{
		{"John", 30},
		{"Jane", 20},
	}
	tempFileName := "temp.csv"
	// 创建临时中间文件
	file, err := os.Create(tempFileName)
	defer file.Close()
	if err != nil {
		panic(err)
	}
	w := csv.NewWriter(file)
	var csvData [][]string
	for _, record := range data {
		row := []string{record.Name, strconv.Itoa(record.Age)}
		csvData = append(csvData, row)
	}
	w.WriteAll(csvData)

	// 读取临时中间文件并通过HTTP发送
	fileBytes, err := ioutil.ReadFile(tempFileName)
	if err != nil {
		panic(err)
	}
	// 在请求时发送文件
	router := chi.NewRouter()
	router.Get("/data", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/octet-stream")
		w.WriteHeader(http.StatusOK)
		w.Write(fileBytes)
	})
	http.ListenAndServe(":8087", router)
}

请求成功:

PS C:\temp> wget http://localhost:8087/data
--2023-06-13 15:34:00--  http://localhost:8087/data
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:8087... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16 [application/octet-stream]
Saving to: 'data'

data                          100%[=================================================>]      16  --.-KB/s    in 0s

2023-06-13 15:34:00 (523 KB/s) - 'data' saved [16/16]

PS C:\temp> cat data
John,30
Jane,20

到目前为止一切顺利 - 现在我想通过以下方式摆脱中间文件:

  • 使用一个内存中的“容器”来存储要写入的CSV数据
  • 将其内容作为字节写入HTTP writer

我仍然不太了解如何使用io.*bufio.*来处理所需的类型:

  • csv.NewWriter()需要一个*os.File并返回一个*csv.Writer
  • 这个*csv.Writer的内容需要以某种方式提取为字节...
  • ...以便传递给http.ResponseWriter.Write()

如何处理这种不同类型的内存文件操作问题的一般机制(以及这种情况下的特殊机制)是什么?

英文:

I need to request, process and format data to CSV, and then send them via a web service on request.

Assuming that the requested and processed data are in data below, I managed to do that using an intermediate temporary file:

package main

import (
	"encoding/csv"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"

	"github.com/go-chi/chi/v5"
)

type Data struct {
	Name string
	Age  int
}

func main() {
	data := []Data{
		{"John", 30},
		{"Jane", 20},
	}
	tempFileName := "temp.csv"
	// create temporary intermediate file
	file, err := os.Create(tempFileName)
	defer file.Close()
	if err != nil {
		panic(err)
	}
	w := csv.NewWriter(file)
	var csvData [][]string
	for _, record := range data {
		row := []string{record.Name, strconv.Itoa(record.Age)}
		csvData = append(csvData, row)
	}
	w.WriteAll(csvData)

	// read temporary intermediate file to send it via HTTP
	fileBytes, err := ioutil.ReadFile(tempFileName)
	if err != nil {
		panic(err)
	}
	// send the file on request
	router := chi.NewRouter()
	router.Get("/data", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/octet-stream")
		w.WriteHeader(http.StatusOK)
		w.Write(fileBytes)
	})
	http.ListenAndServe(":8087", router)
}

The request is successful:

PS C:\temp> wget http://localhost:8087/data
--2023-06-13 15:34:00--  http://localhost:8087/data
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:8087... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16 [application/octet-stream]
Saving to: 'data'
data                          100%[=================================================>]      16  --.-KB/s    in 0s
2023-06-13 15:34:00 (523 KB/s) - 'data' saved [16/16]
PS C:\temp> cat data
John,30
Jane,20

So far so good - now I would like to get rid of the intermediate file by

  • using an in-memory "container" for the CSV data to be written to
  • write its contents as bytes to the HTTP writer

I still do not have a good understanding of how to use io.* and bufio.* to land on the required types:

  • csv.NewWriter() requires an *os.File and returns a *csv.Writer
  • this *csv.Writer contents somehow need to be extracted as byte ...
  • ... in order to feed the http.ResponseWriter.Write()

What are the general mechanics (and the ones specific to this case) to approach such a problem of different types of in-memory file manipulation?

答案1

得分: 4

csv.NewWriter接受一个io.Writer参数,因此你可以将http.ResponseWriter的实例传递给它,而无需将内容写入文件或内存。

以下是示例代码:

package main

import (
	"encoding/csv"
	"fmt"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"
)

type Data struct {
	Name string
	Age  int
}

func main() {
	router := chi.NewRouter()
	router.Get("/data", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/octet-stream")
		w.WriteHeader(http.StatusOK)
		data := []Data{
			{"John", 30},
			{"Jane", 20},
		}
		csvWriter := csv.NewWriter(w)
		var csvData [][]string
		for _, record := range data {
			row := []string{record.Name, strconv.Itoa(record.Age)}
			csvData = append(csvData, row)
		}
		if err := csvWriter.WriteAll(csvData); err != nil {
			// 处理错误
			fmt.Println(err)
		}
	})
	http.ListenAndServe(":8087", router)
}
英文:

csv.NewWriter takes an io.Writer, so you can pass the instance of http.ResponseWriter to it without writing the content to a file or memory.

Here is the demo:

package main

import (
	"encoding/csv"
	"fmt"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"
)

type Data struct {
	Name string
	Age  int
}

func main() {
	router := chi.NewRouter()
	router.Get("/data", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/octet-stream")
		w.WriteHeader(http.StatusOK)
		data := []Data{
			{"John", 30},
			{"Jane", 20},
		}
		csvWriter := csv.NewWriter(w)
		var csvData [][]string
		for _, record := range data {
			row := []string{record.Name, strconv.Itoa(record.Age)}
			csvData = append(csvData, row)
		}
		if err := csvWriter.WriteAll(csvData); err != nil {
			// handle the error
			fmt.Println(err)
		}
	})
	http.ListenAndServe(":8087", router)
}

答案2

得分: 2

感谢JimB的评论(使用bytes.Buffer)以及关于使用指针到Buffer答案,我成功摆脱了临时文件:

package main

import (
	"bytes"
	"encoding/csv"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"
)

type Data struct {
	Name string
	Age  int
}

func main() {
	data := []Data{
		{"John", 30},
		{"Jane", 20},
	}
	var file bytes.Buffer
	w := csv.NewWriter(&file)
	var csvData [][]string
	for _, record := range data {
		row := []string{record.Name, strconv.Itoa(record.Age)}
		csvData = append(csvData, row)
	}
	w.WriteAll(csvData)

	// send the file on request
	router := chi.NewRouter()
	router.Get("/data", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/octet-stream")
		w.WriteHeader(http.StatusOK)
		w.Write(file.Bytes())
	})
	http.ListenAndServe(":8087", router)
}

更好的答案是Zeke Lu的答案

英文:

Thanks to JimB's comment (to use bytes.Buffer) as well as an answer about using pointers to Buffer) I managed to get rid of the temporary file:

package main

import (
	"bytes"
	"encoding/csv"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"
)

type Data struct {
	Name string
	Age  int
}

func main() {
	data := []Data{
		{"John", 30},
		{"Jane", 20},
	}
	var file bytes.Buffer
	w := csv.NewWriter(&file)
	var csvData [][]string
	for _, record := range data {
		row := []string{record.Name, strconv.Itoa(record.Age)}
		csvData = append(csvData, row)
	}
	w.WriteAll(csvData)

	// send the file on request
	router := chi.NewRouter()
	router.Get("/data", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/octet-stream")
		w.WriteHeader(http.StatusOK)
		w.Write(file.Bytes())
	})
	http.ListenAndServe(":8087", router)
}

The better answer is Zeke Lu's one.

huangapple
  • 本文由 发表于 2023年6月13日 21:47:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76465623.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定