~abp998/gwacl/subscription

« back to all changes in this revision

Viewing changes to x509dispatcher.go

  • Committer: Gavin Panella
  • Date: 2013-03-12 17:05:36 UTC
  • mto: (5.3.2 use-dedent)
  • mto: This revision was merged to the branch mainline in revision 8.
  • Revision ID: gavin@gromper.net-20130312170536-b7u1wbzy0tf0bbxa
Reformat with 4 spaces instead of tabs.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
package gwacl
2
 
 
3
 
import (
4
 
    "bufio"
5
 
    "bytes"
6
 
    "fmt"
7
 
    curl "github.com/andelf/go-curl"
8
 
    . "launchpad.net/gwacl/logging"
9
 
    "net/http"
10
 
    "net/textproto"
11
 
)
12
 
 
13
 
type X509Request struct {
14
 
    APIVersion  string
15
 
    Method      string
16
 
    URL         string
17
 
    Payload     []byte
18
 
    ContentType string
19
 
}
20
 
 
21
 
// Print debugging output.
22
 
var verbose = false
23
 
 
24
 
func SetVerbose(newVerbose bool) {
25
 
    verbose = newVerbose
26
 
}
27
 
 
28
 
// newX509RequestGET initializes an X509Request for a GET.  You may still need
29
 
// to set further values.
30
 
func newX509RequestGET(url, apiVersion string) *X509Request {
31
 
    return &X509Request{
32
 
        Method:     "GET",
33
 
        URL:        url,
34
 
        APIVersion: apiVersion,
35
 
    }
36
 
}
37
 
 
38
 
// newX509RequestPOST initializes an X509Request for a POST.  You may still
39
 
// need to set further values.
40
 
func newX509RequestPOST(url, apiVersion string, payload []byte, contentType string) *X509Request {
41
 
    return &X509Request{
42
 
        Method:      "POST",
43
 
        URL:         url,
44
 
        APIVersion:  apiVersion,
45
 
        Payload:     payload,
46
 
        ContentType: contentType,
47
 
    }
48
 
}
49
 
 
50
 
// newX509RequestDELETE initializes an X509Request for a DELETE.
51
 
func newX509RequestDELETE(url, apiVersion string) *X509Request {
52
 
    return &X509Request{
53
 
        Method:     "DELETE",
54
 
        URL:        url,
55
 
        APIVersion: apiVersion,
56
 
    }
57
 
}
58
 
 
59
 
// newX509RequestPUT initializes an X509Request for a PUT.  You may still
60
 
// need to set further values.
61
 
func newX509RequestPUT(url, apiVersion string, payload []byte, contentType string) *X509Request {
62
 
    return &X509Request{
63
 
        Method:      "PUT",
64
 
        URL:         url,
65
 
        APIVersion:  apiVersion,
66
 
        Payload:     payload,
67
 
        ContentType: contentType,
68
 
    }
69
 
}
70
 
 
71
 
type x509Response struct {
72
 
    StatusCode int
73
 
    // TODO: What exactly do we get back?  How will we know its encoding?
74
 
    Body      []byte
75
 
    RawHeader []byte
76
 
    Header    http.Header
77
 
}
78
 
 
79
 
func newX509Response() *x509Response {
80
 
    return &x509Response{
81
 
        Body:      make([]byte, 0),
82
 
        RawHeader: make([]byte, 0),
83
 
    }
84
 
}
85
 
 
86
 
// HTTP/MS-DOS-style line break.
87
 
const crlf = "\r\n"
88
 
 
89
 
// stripPreliminaryResponses takes an HTTP response and returns the final
90
 
// response block.  It leaves out any preceding "Continue" responses or
91
 
// other non-final responses.
92
 
func stripPreliminaryResponses(httpResponse []byte) []byte {
93
 
    blankLine := []byte(crlf + crlf)
94
 
 
95
 
    // The response ends in a blank line, although we don't want to rely on
96
 
    // that without clear documentation of go-curl's header-writing API.
97
 
    // Ignoring that final optional one, where is the last blank line?
98
 
    withoutLastBlank := httpResponse[:len(httpResponse)-len(blankLine)]
99
 
    lastBlank := bytes.LastIndex(withoutLastBlank, blankLine)
100
 
    if lastBlank == -1 {
101
 
        // There are no preliminary responses.  The response is fine as-is.
102
 
        return httpResponse
103
 
    }
104
 
    return httpResponse[lastBlank+len(blankLine):]
105
 
}
106
 
 
107
 
// stripResponseLine takes an HTTP response and returns the part that makes
108
 
// up the MIME header.
109
 
func stripResponseLine(httpHeader []byte) ([]byte, error) {
110
 
    lineBreak := []byte(crlf)
111
 
    newlinePos := bytes.Index(httpHeader, lineBreak)
112
 
    if newlinePos == -1 {
113
 
        msg := fmt.Errorf("HTTP response header lacks leading line: '%s'", httpHeader)
114
 
        return nil, msg
115
 
    }
116
 
    return httpHeader[newlinePos+len(lineBreak):], nil
117
 
}
118
 
 
119
 
// parseHeader parses the raw header as stored in RawHeader, and uses it to
120
 
// initialize Header.
121
 
func (response *x509Response) parseHeader() error {
122
 
    rawHeader := stripPreliminaryResponses(response.RawHeader)
123
 
    mimeHeader, err := stripResponseLine(rawHeader)
124
 
    if err != nil {
125
 
        return err
126
 
    }
127
 
 
128
 
    // textproto.NewReader() parses a MIME header.  But it requires a
129
 
    // bufio.Reader (struct), not an io.Reader (interface).  So wrap the header
130
 
    // contents in an io.Reader using bytes.NewReader(), then wrap that in a
131
 
    // bufio.Reader using bufio.NewReader(), and then pass that to
132
 
    // textproto.NewReader().
133
 
    reader := textproto.NewReader(bufio.NewReader(bytes.NewReader(mimeHeader)))
134
 
    header, err := reader.ReadMIMEHeader()
135
 
    if err != nil {
136
 
        return err
137
 
    }
138
 
    response.Header = http.Header(header)
139
 
    return nil
140
 
}
141
 
 
142
 
func performX509CurlRequest(session *x509Session, request *X509Request) (*x509Response, error) {
143
 
    if verbose {
144
 
        Debug("Performing request")
145
 
        Debug("Request url: " + request.URL)
146
 
        Debug("Request method: " + request.Method)
147
 
        Debugf("Request body: %s\n", request.Payload)
148
 
    }
149
 
    response := newX509Response()
150
 
    ch := request.makeCurlRequest(session, response)
151
 
    defer ch.Cleanup()
152
 
 
153
 
    // TODO: Add timeout provision.
154
 
    // TODO: Get rid of the annoying debug message go-curl spits out
155
 
    // for every single request: "debug (Getinfo) 200".
156
 
    err := ch.Perform()
157
 
    if err != nil {
158
 
        return nil, err
159
 
    }
160
 
 
161
 
    status, err := ch.Getinfo(curl.INFO_RESPONSE_CODE)
162
 
    if err != nil {
163
 
        return nil, err
164
 
    }
165
 
    err = response.parseHeader()
166
 
    if err != nil {
167
 
        return nil, err
168
 
    }
169
 
    response.StatusCode = status.(int)
170
 
    if verbose {
171
 
        Debug("Got response")
172
 
        Debugf("Response status: %d\n", response.StatusCode)
173
 
        Debugf("Response headers: %s\n", response.RawHeader)
174
 
        Debugf("Response body: %s\n", response.Body)
175
 
    }
176
 
    return response, nil
177
 
}
178
 
 
179
 
// The maximum number of redirection-followings allowed.
180
 
var _CURL_MAX_REDIRECTS = 10
181
 
 
182
 
// makeCurlRequest produces a curl.CURL representing the request.
183
 
// Clean up the request by calling its Cleanup() method after you're done
184
 
// with it.
185
 
func (request *X509Request) makeCurlRequest(session *x509Session, response *x509Response) *curl.CURL {
186
 
    ch := curl.EasyInit()
187
 
 
188
 
    ch.Setopt(curl.OPT_SSLCERT, session.certFile)
189
 
    ch.Setopt(curl.OPT_KEYPASSWD, "")
190
 
    ch.Setopt(curl.OPT_URL, request.URL)
191
 
    //ch.Setopt(curl.OPT_VERBOSE, true)
192
 
    ch.Setopt(curl.OPT_FOLLOWLOCATION, true)
193
 
    ch.Setopt(curl.OPT_MAXREDIRS, _CURL_MAX_REDIRECTS)
194
 
    ch.Setopt(curl.OPT_SSLVERSION, 3)
195
 
    versionHeader := "x-ms-version: " + request.APIVersion
196
 
    headers := []string{versionHeader}
197
 
    request.setCurlHTTPMethod(ch)
198
 
 
199
 
    if len(request.Payload) != 0 {
200
 
        headers = append(headers, "Content-Type: "+request.ContentType)
201
 
        // Arrange for the request body to be written.
202
 
        headers = append(headers, "Transfer-Encoding: chunked")
203
 
        ch.Setopt(curl.OPT_READFUNCTION, func(buf []byte, _ interface{}) int {
204
 
            // Buffered request write.  Copy as much of the payload as will
205
 
            // fit into the buffer curl gave us for it, and return the number
206
 
            // of bytes written.  The calls to this callback will stop once
207
 
            // we return zero.
208
 
            // TODO: Test this against various nasty data sizes.
209
 
            bytesCopied := copy(buf, request.Payload)
210
 
            request.Payload = request.Payload[bytesCopied:]
211
 
            return bytesCopied
212
 
        })
213
 
    }
214
 
 
215
 
    // Arrange for the response body to be stored in response.Body.
216
 
    ch.Setopt(curl.OPT_WRITEFUNCTION, func(data []byte, _ interface{}) bool {
217
 
        response.Body = append(response.Body, data...)
218
 
        // Indicate that we're done processing the data.
219
 
        return true
220
 
    })
221
 
 
222
 
    // Arrange for the response header to be stored in response.RawHeader.
223
 
    // After the request we can parse them into response.Header.
224
 
    ch.Setopt(curl.OPT_HEADERFUNCTION, func(data []byte, _ interface{}) bool {
225
 
        response.RawHeader = append(response.RawHeader, data...)
226
 
        // Indicate that we're done processing the data.
227
 
        return true
228
 
    })
229
 
 
230
 
    ch.Setopt(curl.OPT_HTTPHEADER, headers)
231
 
    return ch
232
 
}
233
 
 
234
 
// setCurlHTTPMethod sets up a CURL object to perform an HTTP request of
235
 
// the HTTP method (GET, POST, etc.) appropriate for the request.
236
 
func (request *X509Request) setCurlHTTPMethod(ch *curl.CURL) {
237
 
    switch request.Method {
238
 
    case "GET":
239
 
        // Nothing to be done.  This is the default.
240
 
    case "POST":
241
 
        ch.Setopt(curl.OPT_POST, true)
242
 
    case "PUT":
243
 
        ch.Setopt(curl.OPT_UPLOAD, true)
244
 
    case "DELETE":
245
 
        ch.Setopt(curl.OPT_CUSTOMREQUEST, "DELETE")
246
 
    default:
247
 
        panic(fmt.Errorf("Unsupported http method: %s", request.Method))
248
 
    }
249
 
}