package acme import ( "bytes" "encoding/json" "errors" "log" "net/http" "net/textproto" "regexp" ) type Status int const ( StatusUnknown Status = iota StatusPending StatusProcessing StatusValid StatusInvalid StatusRevoked ) func (s *Status) UnmarshalJSON(b []byte) error { var status = map[string]Status{ "unknown": StatusUnknown, "pending": StatusPending, "processing": StatusProcessing, "valid": StatusValid, "invalid": StatusInvalid, "revoked": StatusRevoked, } if st, ok := status[string(b)]; ok { *s = st return nil } return errors.New("unknown status") } type Signer interface { Sign([]byte) ([]byte, error) parseNonce(*http.Response) } func Get(s Signer, uri string, v interface{}) error { resp, err := http.Get(uri) if err != nil { return err } defer resp.Body.Close() s.parseNonce(resp) return json.NewDecoder(resp.Body).Decode(v) } // Important header fields // Replay-Nonce each response, required for next request // Link links to next stage // Retry-After pooling interval func Post(s Signer, uri string, v interface{}) (*http.Response, error) { body, err := json.Marshal(v) if err != nil { return nil, err } signed, err := s.Sign(body) if err != nil { return nil, err } resp, err := http.Post(uri, "application/jose+json", bytes.NewReader(signed)) if err != nil { return nil, err } s.parseNonce(resp) if resp.StatusCode >= http.StatusBadRequest { return nil, handleError(resp) } log.Printf("%+v\n", parseLinks(resp)) return resp, nil } func parseLinks(r *http.Response) map[string]string { links := make(map[string]string) key := textproto.CanonicalMIMEHeaderKey("link") reg := regexp.MustCompile(`^<(.*)>;rel="(.*)"`) for _, l := range r.Header[key] { re := reg.FindStringSubmatch(l) if len(re) == 3 { links[re[2]] = re[1] } } return links }