英文:
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 asbyte
... - ... 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论