class HttpNetwork implements Network {

    @Override
    public NetworkResponse performRequest(Request<?> request) throws JusError {
        long requestStart = System.nanoTime();
        boolean serverAuthRequested = false;
        boolean proxyAuthRequested = false;
        while (true) {
            if (request.isCanceled()) {
                //it will be handled/ignored later
                return null;
            }
            if (request.getNoConnectionPolicy() != null
                    && request.getConnectivityManager() != null) {
                if (request.getConnectivityManager().getActiveNetwork() == null
                        || !request.getConnectivityManager().getActiveNetwork().isConnected()) {
                    JusError error = request.getNoConnectionPolicy().throwOnNoConnection(request);
                    if (error != null) {
                        throw error;
                    }
                }
            }
            NetworkResponse httpResponse = null;
            try {
                // Gather headers.
                Headers.Builder headers = new Headers.Builder();
                addCacheHeaders(headers, request.getCacheEntry());
                addServerAuthHeaders(request.getServerAuthenticator(), headers);
                addProxyAuthHeaders(request.getProxyAuthenticator(), headers);

                Headers extraHeaders = headers.build();
                request.addMarker(Request.EVENT_NETWORK_STACK_SEND, request.getNetworkRequest(),
                        extraHeaders);
                httpResponse = httpStack.performRequest(request, extraHeaders, pool);
                if (httpResponse == null) {
                    throw new NetworkError("No Response");
                }

                //Check for redirects
                if (request.getRedirectPolicy() != null) {
                    Request newR = request.getRedirectPolicy().verifyRedirect(request,
                            httpResponse);
                    Headers rHeaders = new Headers.Builder().build();
                    while (newR != null) {
                        request.addMarker(Request.EVENT_NETWORK_STACK_REDIRECT_SEND, newR);
                        httpResponse = httpStack.performRequest(newR, rHeaders, pool);
                        request.addMarker(Request.EVENT_NETWORK_STACK_REDIRECT_COMPLETE,
                                httpResponse);
                        newR = request.getRedirectPolicy().verifyRedirect(request, httpResponse);
                    }
                }

                request.addMarker(Request.EVENT_NETWORK_STACK_COMPLETE, httpResponse);

                //currently all requests that came to here normally needs to be attached to the
                // queue
                //however due the complete decoupling of the components in Jus a Network may be set
                //to perform internal requests, i.e. which was not passed to the queue, possibly
                // auth
                //requests. So we shall check
                if (request.getRequestQueue() != null) {
                    httpResponse = request.getRequestQueue().transformResponse(request,
                            httpResponse);
                    request.addMarker(Request.EVENT_NETWORK_TRANSFORM_COMPLETE, httpResponse);
                }

                //check completeness of body
                if (httpResponse != null && httpResponse.headers != null) {
                    String contentLen = httpResponse.headers.get(HTTP.CONTENT_LEN);
                    if (contentLen != null) {
                        int cLen = Integer.parseInt(contentLen);
                        if (cLen > httpResponse.data.length
                                && request.getMethod() != Request.Method.HEAD) {
                            throw new NetworkError(httpResponse, "Response Body not completely " +
                                    "received");
                        }
                    }
                }

                //Check for Auth
                if (httpResponse.statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
                    request.addMarker(Request.EVENT_NETWORK_STACK_AUTH_ERROR,
                            httpResponse);
                    if (request.getServerAuthenticator() != null && !serverAuthRequested) {
                        request.getServerAuthenticator().clearAuthValue();
                        try {
                            //typical implementation would try to refresh the token
                            //after being set to invalid
                            request.getServerAuthenticator().getAuthValue();
                        } catch (AuthError authError) {
                            //finally we didn't succeed
                            throw authError;
                        }
                        //retry the request
                        request.addMarker(Request.EVENT_NETWORK_STACK_AUTH_ERROR_RESEND,
                                httpResponse);
                        serverAuthRequested = true;
                        continue;
                    } else {
                        //or if another way of auth is used
                        throw new AuthError(httpResponse);
                    }
                } else if (httpResponse.statusCode == HttpURLConnection.HTTP_PROXY_AUTH) {
                    request.addMarker(Request.EVENT_NETWORK_STACK_AUTH_PROXY_ERROR,
                            httpResponse);
                    if (request.getProxyAuthenticator() != null && !proxyAuthRequested) {
                        request.getProxyAuthenticator().clearAuthValue();
                        try {
                            //typical implementation would try to refresh the token
                            //after being set to invalid
                            request.getProxyAuthenticator().getAuthValue();
                        } catch (AuthError authError) {
                            //finally we didn't succeed
                            throw authError;
                        }
                        //retry the request
                        request.addMarker(Request.EVENT_NETWORK_STACK_AUTH_ERROR_RESEND,
                                httpResponse);
                        proxyAuthRequested = true;
                        continue;
                    } else {
                        //or if another way of auth is used
                        throw new AuthError(httpResponse);
                    }
                }

                // if the request is slow, log it.
                long requestLifetime = System.nanoTime() - requestStart;
                logSlowRequests(requestLifetime, request, httpResponse.data, httpResponse
                        .statusCode);

                // Handle cache validation.
                if (httpResponse.statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {

                    Entry entry = request.getCacheEntry();
                    if (entry != null) {

                        // A HTTP 304 response does not have all header fields. We
                        // have to use the header fields from the cache entry plus
                        // the new ones from the response.
                        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                        final Map<String, List<String>> responseHeaders = httpResponse.headers.toMultimap();
                        final Map<String, List<String>> cacheHeaders = entry.responseHeaders.toMultimap();
                        Headers.Builder hBuilder = new Headers.Builder();
                        hBuilder.addMMap(responseHeaders);
                        if (cacheHeaders != null) {
                            for (Map.Entry<String, List<String>> hentry : cacheHeaders.entrySet()) {
                                //could be status line
                                if (hentry.getKey() != null
                                        && !responseHeaders.containsKey(hentry.getKey())) {
                                    hBuilder.add(hentry.getKey(), hentry.getValue());
                                }
                            }
                        }

                        httpResponse = new NetworkResponse(
                                httpResponse.statusCode,
                                entry.data, hBuilder.build(),
                                System.nanoTime() - requestStart
                        );
                    } else {
                        httpResponse = new NetworkResponse(
                                httpResponse.statusCode,
                                null,
                                httpResponse.headers,
                                System.nanoTime() - requestStart
                        );
                    }
                } else if (httpResponse.statusCode < 200 || httpResponse.statusCode > 299) {
                    //todo Not really nice throw better
                    throw new IOException();
                }
                return httpResponse;
            } catch (SocketTimeoutException e) {
                attemptRetryOnException("socket", request, new TimeoutError("socket Timeout", e));
            } catch (MalformedURLException e) {
                throw new RuntimeException("Bad URL " + request.getUrlString(), e);
            } catch (IOException e) {
                NetworkResponse networkResponse;
                if (httpResponse != null) {
                    networkResponse = httpResponse;
                } else {
                    throw new NetworkError(e);
                }
                //todo add queue markers
//                JusLog.e("Unexpected response code %d for %s", networkResponse.statusCode,
//                        request.getUrlString());
                if (networkResponse != null) {
                    if (networkResponse.statusCode == HttpURLConnection
                            .HTTP_CLIENT_TIMEOUT) {
                        attemptRetryOnException("http-client", request, new RequestError
                                (networkResponse, "HTTP_CLIENT_TIMEOUT"));
                    } else if (networkResponse.statusCode > 399 && networkResponse.statusCode <
                            500) {
                        //some request query error that does not make sense to retry, assuming
                        // the service we use is deterministic
                        throw new RequestError(networkResponse);
                    } else if (networkResponse.statusCode > 499) {
                        //some server error might not need to be retried
                        //however retry policy set to this request should handle it as it needs.
                        attemptRetryOnException("server",
                                request, new ServerError(networkResponse));
                    } else {
                        //unclassified error
                        throw new JusError(networkResponse, e);
                    }
                } else {
                    //this can result doe to many reasons and the implementations may have some
                    // means to know if a retry is needed
                    attemptRetryOnException("network", request, new NetworkError());
                }
            } catch (AuthError authError) {
                //we have failed to get a token so give it up
                throw authError;
            }
        }
    }

    public  HttpNetwork(HttpStack httpStack);
    public  HttpNetwork(HttpStack httpStack, ByteArrayPool pool);

}