Resolving HTTP Error 416 Requested Range not satisfiable (IIS 7.5 example)

The chaos called HTTP often surprises us with some new sweet little error that put us in not so satisfiable position. For example the anonymous HTTP error 416.

This error is product of mismatch between the client (for example browser) and the server regarding to the Accept-Ranges header tag. In particular this error emerges when the client is requesting a bigger resource similar to pdf files or images and awaits for the response of the server. The initial response could be fine but the streaming of the resource could fail. Why?

Accept-Ranges: bytes means that the request can be served partially. After the initial content-length parameter the server should provides us Content-Range tag (bytes size) in the response with every ‘partial’ request to keep the consistency of the stream. If this doesn’t work right, say hello to error 416. To be noted that this is highly influenced by how the client is implemented, not only the server.

To check if the server supports range header we could do a thing. Send HEAD request instead of GET to the wanted resource. If response 206 is returned range is supported. If the response code is 200 server side should change Accept-Range header to not foul clients.

Curl example: $ curl -i -X HEAD http://address/path/resource

Nevertheless let’s just solve the problem. Usually I notice this error in relation IIS -> Chrome or Apache -> Chrome. But it occurs on Firefox too.

Setting Accept-Bytes: none in IIS: Intenet Information Service (IIS) Manager -> Server Node -> Sites -> Problematic Site -> HTTP Response Headers (In IIS section) -> Add (Action) -> Name: Accept-Bytes; Value: none .

Setting Accept-Bytes: none in Apache2 : Enable apache2 mod_headers. Add “Header set Accept-Ranges none” in configuration of host.

Restart changes to take effect.

Have a nice day : )

Setting Up Context in Apache Tomcat for Serving Static Files

The intro:

So I’ve heard you want to serve static files from your Tomcat Web App in a way that they won’t be deleted on WAR redeploy or Tomcat restarted?

You have a solution, and that is mapping a custom Context in your Apache Tomcat server.xml .

The scenario :

You have a site that allows users to upload images that are public,shared and not under a hood of some security filter. The most intuitive solution is to put them in some directory i.e. ‘uploads’ , but then you realize that the things in the exploded WAR rewrite on redeploy or if the war is in the webapps directory on Tomcat restart. (you can change this behaviour)

The solution is simple: save the files to some directory outside of the war (something like ‘/usr/share/tomcat/uploads’) and map that directory on the server context of your Tomcat AS (something like http://lesite:8080/uploads).

With workaround like this you will see your uploaded cute kitty picture like this: http://lesite:8080/uploads/kitty.jpg

The implementation:

Let’s use the same examples. The mapping is done in <CATALINA_HOME>/conf/server.xml (hopes you know what and where catalina_home is )

This is default situation on new Tomcat install (a snippet from sever.xml):

<Host name=”localhost” appBase=”webapps” unpackWARs=”true” autoDeploy=”true”>

<Valve className=”org.apache.catalina.valves.AccessLogValve” directory=”logs”
prefix=”localhost_access_log.” suffix=”.txt”
pattern=”%h %l %u %t &quot;%r&quot; %s %b” />

</Host>

But we want to change that in this:

<Host name=”localhost” appBase=”webapps” unpackWARs=”true” autoDeploy=”true”>

<Context docBase="/usr/share/tomcat/uploads" path="/uploads" />

<Valve className=”org.apache.catalina.valves.AccessLogValve” directory=”logs”
prefix=”localhost_access_log.” suffix=”.txt”
pattern=”%h %l %u %t &quot;%r&quot; %s %b” />

</Host>

And that’s it, end of setting. Restart , code and redeploy.

The cookie:

Java snippet of simple utilization:

public class UploadsServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletExcpetion, IOException {
        File file = new File("/usr/share/tomcat/uploads", request.getPathInfo());
        response.setHeader("Content-Type", Files.probeContentType(file.toPath()));
        response.setHeader("Content-Length", String.valueOf(file.length()));
        Files.copy(file.toPath(), response.getOutputStream());
    }

}

The conclusion:
In exact things the need for conclusion is deprecated. Everything should be concluded the one way :

return goToTopAndReadAgain();

The hint:

Maybe you won’t be impressed, and probably you have a better solution/implementation for/of the scenario. However let me give you a clue how this can be found useful in different situation. Proxying and load balancing, possibly with Nginx on front and couple of Tomcats behind. Defining new server contextses and getting a feel of that damn Superman speed.

Fiuuuuuuuuuuuuuuuuu…

(salutations and thanks to a friend of mine for collaboration)