diff --git a/http/contract/dataflowInterface.go b/http/contract/dataflowInterface.go index cb0fdb8..3a88cfb 100644 --- a/http/contract/dataflowInterface.go +++ b/http/contract/dataflowInterface.go @@ -4,6 +4,7 @@ import ( "context" "io" "net/http" + "net/textproto" ) // RequestDataflowInterface 是一个 Http 请求构建器, 建议将注释中的私有方法实现到内部 @@ -24,6 +25,7 @@ type RequestDataflowInterface interface { Body(body io.Reader) RequestDataflowInterface Any(data BodyEncoder) RequestDataflowInterface Xml(xmlAny interface{}) RequestDataflowInterface + Multipart(multipartDf func(multipart MultipartDfInterface)) RequestDataflowInterface Err() error @@ -46,3 +48,17 @@ type ResponseHelper interface { GetBodyBytes() ([]byte, error) GetBodyJsonAsMap() (map[string]interface{}, error) } + +type MultipartDfInterface interface { + Boundary(b string) MultipartDfInterface + FileByPath(fieldName string, filePath string) MultipartDfInterface + FileMem(fieldName string, fileName string, reader io.Reader) MultipartDfInterface + Part(header textproto.MIMEHeader, reader io.Reader) MultipartDfInterface + FieldValue(fieldName string, value string) MultipartDfInterface + Field(fieldName string, reader io.Reader) MultipartDfInterface + Close() error + GetBoundary() string + GetReader() io.Reader + GetContentType() string + Err() error +} diff --git a/http/dataflow/dataflow.go b/http/dataflow/dataflow.go index 3288ddd..d54f107 100644 --- a/http/dataflow/dataflow.go +++ b/http/dataflow/dataflow.go @@ -217,6 +217,24 @@ func (d *Dataflow) Xml(xmlAny interface{}) contract.RequestDataflowInterface { return d } +func (d *Dataflow) Multipart(multipartDf func(multipart contract.MultipartDfInterface)) contract.RequestDataflowInterface { + multipart := NewMultipartHelper() + multipartDf(multipart) + err := multipart.Close() + if err != nil { + d.err = append(d.err, err) + return d + } + err = multipart.Err() + if err != nil { + d.err = append(d.err, err) + return d + } + d.Header("content-type", multipart.GetContentType()) + d.Body(multipart.GetReader()) + return d +} + func (d *Dataflow) Err() error { if len(d.err) > 0 { return d.err[0] diff --git a/http/dataflow/dataflow_test.go b/http/dataflow/dataflow_test.go index 12e076f..1e3b53f 100644 --- a/http/dataflow/dataflow_test.go +++ b/http/dataflow/dataflow_test.go @@ -121,3 +121,22 @@ func TestDataflow_Xml(t *testing.T) { t.Error("xml body failed") } } + +func TestDataflow_Multipart(t *testing.T) { + df := InitBaseDataflow() + + df.Multipart(func(multipart contract.MultipartDfInterface) { + mpDataflow := NewMultipartHelper() + mpDataflow.Boundary("test-boundary") + mpDataflow.FieldValue("param1", "value1") + data := strings.NewReader("it's a string reader") + mpDataflow.Field("data", data) + if mpDataflow.Err() != nil { + t.Error(mpDataflow.Err()) + } + }) + + if df.Err() != nil { + t.Error(df.Err()) + } +} diff --git a/http/dataflow/mutlipart.go b/http/dataflow/mutlipart.go new file mode 100644 index 0000000..1b62959 --- /dev/null +++ b/http/dataflow/mutlipart.go @@ -0,0 +1,145 @@ +package dataflow + +import ( + "bytes" + "github.com/ArtisanCloud/PowerLibs/v3/http/contract" + "github.com/pkg/errors" + "io" + "mime/multipart" + "net/textproto" + "os" + "path" +) + +type MultipartDf struct { + buf bytes.Buffer + mWriter *multipart.Writer + errs []error +} + +func NewMultipartHelper() contract.MultipartDfInterface { + df := MultipartDf{} + mWriter := multipart.NewWriter(&df.buf) + df.mWriter = mWriter + return &df +} + +func (m *MultipartDf) Boundary(b string) contract.MultipartDfInterface { + err := m.mWriter.SetBoundary(b) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "set boundary failed")) + } + return m +} + +func (m *MultipartDf) FileByPath(fieldName string, filePath string) contract.MultipartDfInterface { + file, err := os.OpenFile(filePath, os.O_RDONLY, 0644) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create file part failed")) + } + _, fileName := path.Split(filePath) + + writer, err := m.mWriter.CreateFormFile(fieldName, fileName) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create file part failed")) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(file) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create file part failed")) + } + _, err = buf.WriteTo(writer) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create file part failed")) + } + return m +} + +func (m *MultipartDf) FileMem(fieldName string, fileName string, reader io.Reader) contract.MultipartDfInterface { + writer, err := m.mWriter.CreateFormFile(fieldName, fileName) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create file part failed")) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(reader) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create file part failed")) + } + _, err = buf.WriteTo(writer) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create file part failed")) + } + return m +} + +func (m *MultipartDf) Part(header textproto.MIMEHeader, reader io.Reader) contract.MultipartDfInterface { + writer, err := m.mWriter.CreatePart(header) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create part failed")) + return m + } + var buf bytes.Buffer + _, err = buf.ReadFrom(reader) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create part read failed")) + return m + } + _, err = buf.WriteTo(writer) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create part write failed")) + return m + } + return m +} + +func (m *MultipartDf) FieldValue(fieldName string, value string) contract.MultipartDfInterface { + err := m.mWriter.WriteField(fieldName, value) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "set field failed")) + } + return m +} + +func (m *MultipartDf) Field(fieldName string, reader io.Reader) contract.MultipartDfInterface { + writer, err := m.mWriter.CreateFormField(fieldName) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create field failed")) + return m + } + var buf bytes.Buffer + _, err = buf.ReadFrom(reader) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create field read failed")) + return m + } + _, err = buf.WriteTo(writer) + if err != nil { + m.errs = append(m.errs, errors.Wrap(err, "create field write failed")) + return m + } + return m +} + +func (m *MultipartDf) Close() error { + return m.mWriter.Close() +} + +func (m *MultipartDf) GetBoundary() string { + return m.mWriter.Boundary() +} + +func (m *MultipartDf) GetReader() io.Reader { + return &m.buf +} + +func (m *MultipartDf) GetContentType() string { + return m.mWriter.FormDataContentType() +} + +func (m *MultipartDf) Err() error { + if len(m.errs) == 0 { + return nil + } else { + return m.errs[0] + } +} diff --git a/http/helper/example_test.go b/http/helper/example_test.go index 0850dc3..838e003 100644 --- a/http/helper/example_test.go +++ b/http/helper/example_test.go @@ -5,6 +5,8 @@ import ( "github.com/ArtisanCloud/PowerLibs/v3/http/contract" "log" "net/http" + "strings" + "testing" ) func ExampleRequestHelper_WithMiddleware() { @@ -160,3 +162,25 @@ func ExampleHttpDebugMiddleware() { // "status": "success" //} } + +func TestRequestHelper_Df_Multipart(t *testing.T) { + helper, err := NewRequestHelper(&Config{}) + if err != nil { + t.Error(err) + } + + _, err = helper.Df().Method(http.MethodPost). + Url("https://typedwebhook.tools/webhook"). + Multipart(func(multipart contract.MultipartDfInterface) { + data := strings.NewReader("test data") + multipart.Boundary("test-boundary"). + //FileByPath("file", "README.md"). + FieldValue("param1", "value1"). + FieldValue("param2", "value2"). + Field("data", data) + }).Request() + + if err != nil { + t.Error(err) + } +}