| package goscaffold |
| |
| import ( |
| "net/http" |
| "regexp" |
| "sort" |
| "strconv" |
| "strings" |
| ) |
| |
| var qParamRe = regexp.MustCompile("^q=([0-9\\.]+)$") |
| |
| type acceptCriterion struct { |
| major string |
| minor string |
| precedence float32 |
| origOrder int |
| match string |
| } |
| type acceptCriteria []acceptCriterion |
| |
| /* |
| SelectMediaType matches a set of candidate media types against the |
| Accept header in an HTTP request. It does this using the rules from |
| RFC2616 section 1.4. |
| If no Accept header is present, then the first choice in the |
| "choices" array is returned. |
| If multiple choices match, then we will take the first one specified |
| in the "Accept" header. |
| If there are no compatible media types, then an empty string |
| is returned. |
| Only the first "Accept" header on the request is considered. |
| */ |
| func SelectMediaType(req *http.Request, choices []string) string { |
| hdr := strings.TrimSpace(req.Header.Get("Accept")) |
| if hdr == "" || hdr == "*" || hdr == "*/*" { |
| if len(choices) >= 1 { |
| return choices[0] |
| } |
| return "" |
| } |
| |
| // Parse accept header and parse the criteria |
| candidates := parseAcceptHeader(hdr) |
| |
| // For each choice, assign the best match (least wildcardy) |
| matches := make(map[string]*acceptCriterion) |
| for _, choice := range choices { |
| for c := range candidates { |
| crit := candidates[c] |
| if crit.matches(choice) { |
| if matches[choice] == nil || |
| matches[choice].level() < crit.level() { |
| matches[choice] = &acceptCriterion{ |
| minor: crit.minor, |
| major: crit.major, |
| precedence: crit.precedence, |
| origOrder: crit.origOrder, |
| match: choice, |
| } |
| } |
| } |
| } |
| } |
| |
| if len(matches) == 0 { |
| return "" |
| } |
| |
| // Sort the matches now by precedence level and original order |
| var sortedMatches acceptCriteria |
| for _, v := range matches { |
| sortedMatches = append(sortedMatches, *v) |
| } |
| sortedMatches.sort() |
| |
| return sortedMatches[0].match |
| } |
| |
| func parseAcceptHeader(hdr string) acceptCriteria { |
| var ret acceptCriteria |
| parts := strings.Split(hdr, ",") |
| for i, part := range parts { |
| candidate := parseAcceptPart(part, i) |
| ret = append(ret, candidate) |
| } |
| return ret |
| } |
| |
| /* |
| parseAcceptPart parses a single section of the header and extracts the |
| "q" parameter. |
| */ |
| func parseAcceptPart(part string, order int) acceptCriterion { |
| params := strings.Split(part, ";") |
| finalType := strings.TrimSpace(params[0]) |
| var precedence float32 = 1.0 |
| |
| for _, param := range params[1:] { |
| match := qParamRe.FindStringSubmatch(strings.TrimSpace(param)) |
| if match == nil { |
| finalType += ";" + param |
| } else { |
| qVal, err := strconv.ParseFloat(match[1], 32) |
| if err == nil { |
| precedence = float32(qVal) |
| } |
| } |
| } |
| |
| splitType := strings.SplitN(finalType, "/", 2) |
| |
| return acceptCriterion{ |
| major: splitType[0], |
| minor: splitType[1], |
| precedence: precedence, |
| origOrder: order, |
| } |
| } |
| |
| /* |
| matches matches a candidate media type with a criterion. |
| */ |
| func (a acceptCriterion) matches(t string) bool { |
| st := strings.SplitN(t, "/", 2) |
| |
| if a.major != "*" && a.major != st[0] { |
| return false |
| } |
| if a.minor != "*" && a.minor != st[1] { |
| return false |
| } |
| return true |
| } |
| func (a acceptCriterion) level() int { |
| if a.minor == "*" { |
| if a.major == "*" { |
| return 0 |
| } |
| return 1 |
| } |
| return 2 |
| } |
| |
| /* |
| sortCandidates sorts accept header candidates in order of: |
| 1) Precedence (from the "q" parameter) |
| 2) Original order in accept header |
| 3) Stable sort otherwise |
| */ |
| func (c acceptCriteria) sort() { |
| sort.Stable(c) |
| } |
| |
| func (c acceptCriteria) Less(i, j int) bool { |
| // Higher precedence goes first |
| if c[i].precedence > c[j].precedence { |
| return true |
| } |
| if c[i].precedence == c[j].precedence && |
| c[i].origOrder < c[j].origOrder { |
| return true |
| } |
| return false |
| } |
| |
| func (c acceptCriteria) Len() int { |
| return len(c) |
| } |
| |
| func (c acceptCriteria) Swap(i, j int) { |
| tmp := c[i] |
| c[i] = c[j] |
| c[j] = tmp |
| } |