3

For pagination purposes, our UI guy is specifying the items ranges in the Http header as follows:

Range: items=0-15

In subsequent requests the ranges from the web can be either

Range: items=16-31
Range: items=32-45

...etc...etc

In my controller SearchRequestController.java, I am trying to extract these ranges so that I can send it over to the Solr server to return the values in the requested chucks by setting the start and the offset.

What I am doing is as follows(In SearchRequestController.java):

@RequestMapping("/billsSearch")
@ResponseBody
public SearchResult searchBills(HttpServletRequest request,@RequestBody SearchRequest searchRequest){
    String rangeRequest = request.getHeader("Range");
    //(1)Parse the rangeRequest using some mechanism
    //(2)Set the start using the first value of the range
    //(3) Add the second value to the start to get the offset
    searchRequest.setStart(startValue);
    searchRequest.setOffset(offsetValue);
    return searchHelper(request,searchRequest);
}

There are a couple of questions that I have: Is this the best way to request data in a chunked fashion from the server?

How do I extract the ranges from the request header?

I am assuming the request.getHeader("Range") will return "items=16-31".

Is regular expression the best way to grab 16 and 31 from this string?

If I decide to use regular expression, wouldn't the expression break if I change the range header to be billItems=16-31?

I am a regex n00b, so it is quite possible I am thinking of this in a wrong way.

Are there alternatives to regex to parse range information like this from the http headers within SpringMVC?

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
sc_ray
  • 7,803
  • 11
  • 63
  • 100

3 Answers3

3

Simple range header parser that conforms to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35

class Range {
    Integer start;
    Integer end;
    Integer suffixLength;
}

public class Main {

    public static void main(String[] args) {
        String input = "bytes=33-22,-333,20-,-55,43-0002";
        for (Range range : decodeRange(input)) {
            if (range.suffixLength == null) {
                System.out.printf("Range: %d->%s\n", range.start, range.end == null ? "enf of file" : range.end);
            } else {
                System.out.printf("Last %d bytes\n", range.suffixLength);
            }
        }
    }

    public static List<Range> decodeRange(String rangeHeader) {
        List<Range> ranges = new ArrayList<>();
        String byteRangeSetRegex = "(((?<byteRangeSpec>(?<firstBytePos>\\d+)-(?<lastBytePos>\\d+)?)|(?<suffixByteRangeSpec>-(?<suffixLength>\\d+)))(,|$))";
        String byteRangesSpecifierRegex = "bytes=(?<byteRangeSet>" + byteRangeSetRegex + "{1,})";
        Pattern byteRangeSetPattern = Pattern.compile(byteRangeSetRegex);
        Pattern byteRangesSpecifierPattern = Pattern.compile(byteRangesSpecifierRegex);
        Matcher byteRangesSpecifierMatcher = byteRangesSpecifierPattern.matcher(rangeHeader);
        if (byteRangesSpecifierMatcher.matches()) {
            String byteRangeSet = byteRangesSpecifierMatcher.group("byteRangeSet");
            Matcher byteRangeSetMatcher = byteRangeSetPattern.matcher(byteRangeSet);
            while (byteRangeSetMatcher.find()) {
                Range range = new Range();
                if (byteRangeSetMatcher.group("byteRangeSpec") != null) {
                    String start = byteRangeSetMatcher.group("firstBytePos");
                    String end = byteRangeSetMatcher.group("lastBytePos");
                    range.start = Integer.valueOf(start);
                    range.end = end == null ? null : Integer.valueOf(end);
                } else if (byteRangeSetMatcher.group("suffixByteRangeSpec") != null) {
                    range.suffixLength = Integer.valueOf(byteRangeSetMatcher.group("suffixLength"));
                } else {
                    throw new RuntimeException("Invalid range header");
                }
                ranges.add(range);
            }
        } else {
            throw new RuntimeException("Invalid range header");
        }
        return ranges;
    }
};

or take a look here

Community
  • 1
  • 1
gliviu
  • 81
  • 4
2

Is this the best way to request data in a chunked fashion from the server?

Another approach is to use request parameters:

/billsSearch?from=15&to=31

BTW you can use @RequestHeader annotation:

public SearchResult searchBills(
        @RequestBody SearchRequest searchRequest,
        @RequestHeader("Range") String range) {
    //...
}

How do I extract the ranges from the request header? [...] Is regular expression the best way to grab 16 and 31 from this string?

I would cut the trailing "items=" string, split on - character and parse two resulting numbers:

String[] ranges = range.substring("items=".length()).split("-");
int from = Integer.valueOf(ranges[0]);
int to = Integer.valueOf(ranges[1]);
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
1

Both @RequestParameter and @RequestHeader arguments are subject to type conversion if the target type is not string. If you had a Range type, you could create a Converter<String, Range> and register it with the conversionService. Then your controller would look a little cleaner:

    public SearchResult searchBills(
            @RequestBody SearchRequest searchRequest,
            @RequestHeader Range range) {
        //...
    }

This would useful if it occurred commonly in controllers. Or if it's just one place doing the parsing inside the controller method is good enough.

Rossen Stoyanchev
  • 4,910
  • 23
  • 26