Java tutorial
package org.eclipse.aether.transport.http; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHeaders; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScope; import org.apache.http.auth.params.AuthParams; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpResponseException; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpOptions; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.DateUtils; import org.apache.http.client.utils.URIUtils; import org.apache.http.conn.params.ConnRouteParams; import org.apache.http.entity.AbstractHttpEntity; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.DecompressingHttpClient; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.util.EntityUtils; import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.AuthenticationContext; import org.eclipse.aether.repository.Proxy; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.spi.connector.transport.AbstractTransporter; import org.eclipse.aether.spi.connector.transport.GetTask; import org.eclipse.aether.spi.connector.transport.PeekTask; import org.eclipse.aether.spi.connector.transport.PutTask; import org.eclipse.aether.spi.connector.transport.TransportTask; import org.eclipse.aether.transfer.NoTransporterException; import org.eclipse.aether.transfer.TransferCancelledException; import org.eclipse.aether.util.ConfigUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A transporter for HTTP/HTTPS. */ final class HttpTransporter extends AbstractTransporter { private static final Pattern CONTENT_RANGE_PATTERN = Pattern .compile("\\s*bytes\\s+([0-9]+)\\s*-\\s*([0-9]+)\\s*/.*"); private static final Logger LOGGER = LoggerFactory.getLogger(HttpTransporter.class); private final AuthenticationContext repoAuthContext; private final AuthenticationContext proxyAuthContext; private final URI baseUri; private final HttpHost server; private final HttpHost proxy; private final HttpClient client; private final Map<?, ?> headers; private final LocalState state; HttpTransporter(RemoteRepository repository, RepositorySystemSession session) throws NoTransporterException { if (!"http".equalsIgnoreCase(repository.getProtocol()) && !"https".equalsIgnoreCase(repository.getProtocol())) { throw new NoTransporterException(repository); } try { baseUri = new URI(repository.getUrl()).parseServerAuthority(); if (baseUri.isOpaque()) { throw new URISyntaxException(repository.getUrl(), "URL must not be opaque"); } server = URIUtils.extractHost(baseUri); if (server == null) { throw new URISyntaxException(repository.getUrl(), "URL lacks host name"); } } catch (URISyntaxException e) { throw new NoTransporterException(repository, e.getMessage(), e); } proxy = toHost(repository.getProxy()); repoAuthContext = AuthenticationContext.forRepository(session, repository); proxyAuthContext = AuthenticationContext.forProxy(session, repository); state = new LocalState(session, repository, new SslConfig(session, repoAuthContext)); headers = ConfigUtils.getMap(session, Collections.emptyMap(), ConfigurationProperties.HTTP_HEADERS + "." + repository.getId(), ConfigurationProperties.HTTP_HEADERS); DefaultHttpClient client = new DefaultHttpClient(state.getConnectionManager()); configureClient(client.getParams(), session, repository, proxy); client.setCredentialsProvider(toCredentialsProvider(server, repoAuthContext, proxy, proxyAuthContext)); this.client = new DecompressingHttpClient(client); } private static HttpHost toHost(Proxy proxy) { HttpHost host = null; if (proxy != null) { host = new HttpHost(proxy.getHost(), proxy.getPort()); } return host; } private static void configureClient(HttpParams params, RepositorySystemSession session, RemoteRepository repository, HttpHost proxy) { AuthParams.setCredentialCharset(params, ConfigUtils.getString(session, ConfigurationProperties.DEFAULT_HTTP_CREDENTIAL_ENCODING, ConfigurationProperties.HTTP_CREDENTIAL_ENCODING + "." + repository.getId(), ConfigurationProperties.HTTP_CREDENTIAL_ENCODING)); ConnRouteParams.setDefaultProxy(params, proxy); HttpConnectionParams.setConnectionTimeout(params, ConfigUtils.getInteger(session, ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT, ConfigurationProperties.CONNECT_TIMEOUT + "." + repository.getId(), ConfigurationProperties.CONNECT_TIMEOUT)); HttpConnectionParams.setSoTimeout(params, ConfigUtils.getInteger(session, ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT, ConfigurationProperties.REQUEST_TIMEOUT + "." + repository.getId(), ConfigurationProperties.REQUEST_TIMEOUT)); HttpProtocolParams.setUserAgent(params, ConfigUtils.getString(session, ConfigurationProperties.DEFAULT_USER_AGENT, ConfigurationProperties.USER_AGENT)); } private static CredentialsProvider toCredentialsProvider(HttpHost server, AuthenticationContext serverAuthCtx, HttpHost proxy, AuthenticationContext proxyAuthCtx) { CredentialsProvider provider = toCredentialsProvider(server.getHostName(), AuthScope.ANY_PORT, serverAuthCtx); if (proxy != null) { CredentialsProvider p = toCredentialsProvider(proxy.getHostName(), proxy.getPort(), proxyAuthCtx); provider = new DemuxCredentialsProvider(provider, p, proxy); } return provider; } private static CredentialsProvider toCredentialsProvider(String host, int port, AuthenticationContext ctx) { DeferredCredentialsProvider provider = new DeferredCredentialsProvider(); if (ctx != null) { AuthScope basicScope = new AuthScope(host, port); provider.setCredentials(basicScope, new DeferredCredentialsProvider.BasicFactory(ctx)); AuthScope ntlmScope = new AuthScope(host, port, AuthScope.ANY_REALM, "ntlm"); provider.setCredentials(ntlmScope, new DeferredCredentialsProvider.NtlmFactory(ctx)); } return provider; } LocalState getState() { return state; } private URI resolve(TransportTask task) { return UriUtils.resolve(baseUri, task.getLocation()); } public int classify(Throwable error) { if (error instanceof HttpResponseException && ((HttpResponseException) error).getStatusCode() == HttpStatus.SC_NOT_FOUND) { return ERROR_NOT_FOUND; } return ERROR_OTHER; } @Override protected void implPeek(PeekTask task) throws Exception { HttpHead request = commonHeaders(new HttpHead(resolve(task))); execute(request, null); } @Override protected void implGet(GetTask task) throws Exception { EntityGetter getter = new EntityGetter(task); HttpGet request = commonHeaders(new HttpGet(resolve(task))); resume(request, task); try { execute(request, getter); } catch (HttpResponseException e) { if (e.getStatusCode() == HttpStatus.SC_PRECONDITION_FAILED && request.containsHeader(HttpHeaders.RANGE)) { request = commonHeaders(new HttpGet(request.getURI())); execute(request, getter); return; } throw e; } } @Override protected void implPut(PutTask task) throws Exception { PutTaskEntity entity = new PutTaskEntity(task); HttpPut request = commonHeaders(entity(new HttpPut(resolve(task)), entity)); try { execute(request, null); } catch (HttpResponseException e) { if (e.getStatusCode() == HttpStatus.SC_EXPECTATION_FAILED && request.containsHeader(HttpHeaders.EXPECT)) { state.setExpectContinue(false); request = commonHeaders(entity(new HttpPut(request.getURI()), entity)); execute(request, null); return; } throw e; } } private void execute(HttpUriRequest request, EntityGetter getter) throws Exception { try { SharingHttpContext context = new SharingHttpContext(state); prepare(request, context); HttpResponse response = client.execute(server, request, context); try { context.close(); handleStatus(response); if (getter != null) { getter.handle(response); } } finally { EntityUtils.consumeQuietly(response.getEntity()); } } catch (IOException e) { if (e.getCause() instanceof TransferCancelledException) { throw (Exception) e.getCause(); } throw e; } } private void prepare(HttpUriRequest request, SharingHttpContext context) { boolean put = HttpPut.METHOD_NAME.equalsIgnoreCase(request.getMethod()); if (state.getWebDav() == null && (put || isPayloadPresent(request))) { try { HttpOptions req = commonHeaders(new HttpOptions(request.getURI())); HttpResponse response = client.execute(server, req, context); state.setWebDav(isWebDav(response)); EntityUtils.consumeQuietly(response.getEntity()); } catch (IOException e) { LOGGER.debug("Failed to prepare HTTP context", e); } } if (put && Boolean.TRUE.equals(state.getWebDav())) { mkdirs(request.getURI(), context); } } private boolean isWebDav(HttpResponse response) { return response.containsHeader(HttpHeaders.DAV); } private void mkdirs(URI uri, SharingHttpContext context) { List<URI> dirs = UriUtils.getDirectories(baseUri, uri); int index = 0; for (; index < dirs.size(); index++) { try { HttpResponse response = client.execute(server, commonHeaders(new HttpMkCol(dirs.get(index))), context); try { int status = response.getStatusLine().getStatusCode(); if (status < 300 || status == HttpStatus.SC_METHOD_NOT_ALLOWED) { break; } else if (status == HttpStatus.SC_CONFLICT) { continue; } handleStatus(response); } finally { EntityUtils.consumeQuietly(response.getEntity()); } } catch (IOException e) { LOGGER.debug("Failed to create parent directory {}", dirs.get(index), e); return; } } for (index--; index >= 0; index--) { try { HttpResponse response = client.execute(server, commonHeaders(new HttpMkCol(dirs.get(index))), context); try { handleStatus(response); } finally { EntityUtils.consumeQuietly(response.getEntity()); } } catch (IOException e) { LOGGER.debug("Failed to create parent directory {}", dirs.get(index), e); return; } } } private <T extends HttpEntityEnclosingRequest> T entity(T request, HttpEntity entity) { request.setEntity(entity); return request; } private boolean isPayloadPresent(HttpUriRequest request) { if (request instanceof HttpEntityEnclosingRequest) { HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); return entity != null && entity.getContentLength() != 0; } return false; } private <T extends HttpUriRequest> T commonHeaders(T request) { request.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store"); request.setHeader(HttpHeaders.PRAGMA, "no-cache"); if (state.isExpectContinue() && isPayloadPresent(request)) { request.setHeader(HttpHeaders.EXPECT, "100-continue"); } for (Map.Entry<?, ?> entry : headers.entrySet()) { if (!(entry.getKey() instanceof String)) { continue; } if (entry.getValue() instanceof String) { request.setHeader(entry.getKey().toString(), entry.getValue().toString()); } else { request.removeHeaders(entry.getKey().toString()); } } if (!state.isExpectContinue()) { request.removeHeaders(HttpHeaders.EXPECT); } return request; } private <T extends HttpUriRequest> T resume(T request, GetTask task) { long resumeOffset = task.getResumeOffset(); if (resumeOffset > 0L && task.getDataFile() != null) { request.setHeader(HttpHeaders.RANGE, "bytes=" + Long.toString(resumeOffset) + '-'); request.setHeader(HttpHeaders.IF_UNMODIFIED_SINCE, DateUtils.formatDate(new Date(task.getDataFile().lastModified() - 60L * 1000L))); request.setHeader(HttpHeaders.ACCEPT_ENCODING, "identity"); } return request; } private void handleStatus(HttpResponse response) throws HttpResponseException { int status = response.getStatusLine().getStatusCode(); if (status >= 300) { throw new HttpResponseException(status, response.getStatusLine().getReasonPhrase() + " (" + status + ")"); } } @Override protected void implClose() { AuthenticationContext.close(repoAuthContext); AuthenticationContext.close(proxyAuthContext); state.close(); } private class EntityGetter { private final GetTask task; EntityGetter(GetTask task) { this.task = task; } public void handle(HttpResponse response) throws IOException, TransferCancelledException { HttpEntity entity = response.getEntity(); if (entity == null) { entity = new ByteArrayEntity(new byte[0]); } long offset = 0L, length = entity.getContentLength(); String range = getHeader(response, HttpHeaders.CONTENT_RANGE); if (range != null) { Matcher m = CONTENT_RANGE_PATTERN.matcher(range); if (!m.matches()) { throw new IOException("Invalid Content-Range header for partial download: " + range); } offset = Long.parseLong(m.group(1)); length = Long.parseLong(m.group(2)) + 1L; if (offset < 0L || offset >= length || (offset > 0L && offset != task.getResumeOffset())) { throw new IOException("Invalid Content-Range header for partial download from offset " + task.getResumeOffset() + ": " + range); } } InputStream is = entity.getContent(); utilGet(task, is, true, length, offset > 0L); extractChecksums(response); } private void extractChecksums(HttpResponse response) { // Nexus-style, ETag: "{SHA1{d40d68ba1f88d8e9b0040f175a6ff41928abd5e7}}" String etag = getHeader(response, HttpHeaders.ETAG); if (etag != null) { int start = etag.indexOf("SHA1{"), end = etag.indexOf("}", start + 5); if (start >= 0 && end > start) { task.setChecksum("SHA-1", etag.substring(start + 5, end)); } } } private String getHeader(HttpResponse response, String name) { Header header = response.getFirstHeader(name); return (header != null) ? header.getValue() : null; } } private class PutTaskEntity extends AbstractHttpEntity { private final PutTask task; PutTaskEntity(PutTask task) { this.task = task; } public boolean isRepeatable() { return true; } public boolean isStreaming() { return false; } public long getContentLength() { return task.getDataLength(); } public InputStream getContent() throws IOException { return task.newInputStream(); } public void writeTo(OutputStream os) throws IOException { try { utilPut(task, os, false); } catch (TransferCancelledException e) { throw (IOException) new InterruptedIOException().initCause(e); } } } }