-
Notifications
You must be signed in to change notification settings - Fork 0
/
multipart_form_data.go
126 lines (116 loc) · 4.07 KB
/
multipart_form_data.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package check
import (
"bytes"
"fmt"
"github.com/google/uuid"
"io"
"math/rand"
"mime/multipart"
"net/http"
"net/textproto"
"time"
)
func multipart_form_data() Check {
return Check{
Name: getCheckName(),
run: func(config *Config, runCheckResultCh chan<- RunCheckResult) {
defer close(runCheckResultCh)
serverUrl, ok, stopServerIfNeed := prepareServerUrl(config, runCheckResultCh)
if !ok {
return
}
defer stopServerIfNeed()
postHttpClient := newHTTPClient(config.Protocol, config.TlsSkipVerifyCert)
defer postHttpClient.CloseIdleConnections()
getHttpClient := newHTTPClient(config.Protocol, config.TlsSkipVerifyCert)
defer getHttpClient.CloseIdleConnections()
path := "/" + uuid.NewString()
url := serverUrl + path
contentBytes := func() []byte {
var buff [8 * 1024 * 1024]byte
if _, err := io.ReadFull(rand.New(rand.NewSource(11)), buff[:]); err != nil {
panic(err)
}
return buff[:]
}()
multipartHeaderContentType := "application/octet-stream"
multipartHeaderDisposition := `form-data; name="input_data"`
bodyBuffer := new(bytes.Buffer)
multipartWriter := multipart.NewWriter(bodyBuffer)
multipartHeader := make(textproto.MIMEHeader)
multipartHeader.Set("Content-Type", multipartHeaderContentType)
multipartHeader.Set("Content-Disposition", multipartHeaderDisposition)
contentType := multipartWriter.FormDataContentType()
part, err := multipartWriter.CreatePart(multipartHeader)
if err != nil {
runCheckResultCh <- NewRunCheckResultWithOneError(NewError("failed to create part", err))
return
}
if _, err = io.Copy(part, bytes.NewReader(contentBytes)); err != nil {
runCheckResultCh <- NewRunCheckResultWithOneError(NewError("failed to write content to part", err))
return
}
if err = multipartWriter.Close(); err != nil {
runCheckResultCh <- NewRunCheckResultWithOneError(NewError("failed to close part", err))
return
}
postRespArrived := make(chan struct{}, 1)
postFinished := make(chan struct{})
go func() {
defer func() { postFinished <- struct{}{} }()
postReq, err := http.NewRequest("POST", url, bodyBuffer)
if err != nil {
runCheckResultCh <- NewRunCheckResultWithOneError(NewError("failed to create request", err))
return
}
ensureContentLengthExits(postReq)
postReq.Header.Set("Content-Type", contentType)
_, postOk := sendOrGetAndCheck(postHttpClient, postReq, config.Protocol, runCheckResultCh)
if !postOk {
return
}
postRespArrived <- struct{}{}
}()
select {
case <-postRespArrived:
case <-time.After(config.SenderResponseBeforeReceiverTimeout):
}
getRespCh := make(chan *http.Response)
getFinished := make(chan struct{})
go func() {
defer func() { getFinished <- struct{}{} }()
getReq, err := http.NewRequest("GET", url, nil)
if err != nil {
runCheckResultCh <- NewRunCheckResultWithOneError(NewError("failed to create request", err))
return
}
getResp, getOk := sendOrGetAndCheck(getHttpClient, getReq, config.Protocol, runCheckResultCh)
if !getOk {
return
}
getRespCh <- getResp
}()
var getResp *http.Response
select {
case getResp = <-getRespCh:
case <-time.After(config.GetResponseReceivedTimeout):
runCheckResultCh <- NewRunCheckResultWithOneError(NewError(fmt.Sprintf("failed to get receiver's response in %s", config.GetResponseReceivedTimeout), nil))
return
}
checkContentTypeForwarding(getResp, multipartHeaderContentType, runCheckResultCh)
checkContentDispositionForwarding(getResp, multipartHeaderDisposition, runCheckResultCh)
getBodyBytes, err := io.ReadAll(getResp.Body)
if err != nil {
runCheckResultCh <- NewRunCheckResultWithOneError(NewError("failed to read GET body", err))
return
}
if !bytes.Equal(getBodyBytes, contentBytes) {
runCheckResultCh <- RunCheckResult{SubCheckName: SubCheckNameTransferred, Errors: []ResultError{NewError("different body", nil)}}
return
}
<-postFinished
runCheckResultCh <- RunCheckResult{SubCheckName: SubCheckNameTransferred}
return
},
}
}