|
|
@@ -1,232 +1,232 @@
|
|
|
-/*
|
|
|
- * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
- * @copyright Ranides Atterwim
|
|
|
- * @license WTFPL
|
|
|
- * @url http://ranides.net/projects/assira
|
|
|
- */
|
|
|
-package net.ranides.assira.io.uri.impl;
|
|
|
-
|
|
|
-import java.io.IOException;
|
|
|
-import java.io.InputStream;
|
|
|
-import java.io.OutputStream;
|
|
|
-import java.net.HttpURLConnection;
|
|
|
-import java.net.MalformedURLException;
|
|
|
-import java.net.URI;
|
|
|
-import java.nio.charset.Charset;
|
|
|
-import java.time.Instant;
|
|
|
-import java.util.Arrays;
|
|
|
-import java.util.Collection;
|
|
|
-import java.util.Date;
|
|
|
-import java.util.Set;
|
|
|
-import java.util.regex.Matcher;
|
|
|
-import java.util.regex.Pattern;
|
|
|
-import javax.xml.bind.DatatypeConverter;
|
|
|
-import net.ranides.assira.collection.maps.GenericMap;
|
|
|
-import net.ranides.assira.collection.query.CQuery;
|
|
|
-import net.ranides.assira.credentials.UserProperty;
|
|
|
-import net.ranides.assira.io.IOHandle;
|
|
|
-import net.ranides.assira.io.IOStreams;
|
|
|
-import net.ranides.assira.io.uri.URITime;
|
|
|
-import net.ranides.assira.io.uri.URIFlags;
|
|
|
-import net.ranides.assira.io.uri.URIHandle;
|
|
|
-import net.ranides.assira.io.uri.URIHandler;
|
|
|
-import net.ranides.assira.io.uri.URIResolver;
|
|
|
-import net.ranides.assira.text.Charsets;
|
|
|
-import net.ranides.assira.text.StringTraits;
|
|
|
-import net.ranides.assira.trace.LoggerUtils;
|
|
|
-import org.slf4j.Logger;
|
|
|
-
|
|
|
-/**
|
|
|
- *
|
|
|
- * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
- */
|
|
|
-public class CHTTPHandler implements URIHandler {
|
|
|
-
|
|
|
- private static final Logger LOGGER = LoggerUtils.getLogger();
|
|
|
-
|
|
|
- private static final Pattern RE_CHARSET = Pattern.compile(";[ ]*charset=([^; ]+)");
|
|
|
-
|
|
|
- private final URIResolver resolver;
|
|
|
-
|
|
|
- public CHTTPHandler() {
|
|
|
- this.resolver = URIResolver.DEFAULT;
|
|
|
- }
|
|
|
-
|
|
|
- public CHTTPHandler(URIResolver resolver) {
|
|
|
- this.resolver = resolver;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public URIHandle resolve(URI uri) throws IOException {
|
|
|
- return new CHttpHandle(uri);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Collection<String> schemes() {
|
|
|
- return Arrays.asList("http","https");
|
|
|
- }
|
|
|
-
|
|
|
- private class CHttpHandle extends CHandle {
|
|
|
-
|
|
|
- private final URI uri;
|
|
|
- private final IOHandle<HttpURLConnection> head;
|
|
|
- private final IOHandle<HttpURLConnection> get;
|
|
|
- private final IOHandle<HttpURLConnection> post;
|
|
|
-
|
|
|
- public CHttpHandle(URI uri) throws MalformedURLException {
|
|
|
- this.uri = uri;
|
|
|
- head = new IOHandle<>(() -> connect("HEAD"));
|
|
|
- get = new IOHandle<>(() -> connect("GET"));
|
|
|
- post = new IOHandle<>(() -> connect("POST"));
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public URIResolver resolver() {
|
|
|
- return resolver;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public URI uri() throws IOException {
|
|
|
- return uri;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Charset charset() throws IOException {
|
|
|
- return getContentCharset(head().getContentType());
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Set<URIFlags> flags() throws IOException {
|
|
|
- Set<URIFlags> re = URIFlags.collect(URIFlags.FILE);
|
|
|
- try {
|
|
|
- HttpURLConnection c = head();
|
|
|
- if(isSuccess(c)) {
|
|
|
- re.add(URIFlags.EXISTS);
|
|
|
- re.add(URIFlags.READABLE);
|
|
|
- }
|
|
|
-
|
|
|
- if(StringTraits.starts(c.getContentType(), "text/")) {
|
|
|
- re.add(URIFlags.TEXT);
|
|
|
- } else {
|
|
|
- re.add(URIFlags.BINARY);
|
|
|
- }
|
|
|
- if(c.getContentLengthLong() >= 0) {
|
|
|
- re.add(URIFlags.SIZE);
|
|
|
- }
|
|
|
- if(StringTraits.contains(c.getHeaderField("Accept-Ranges"), "bytes")) {
|
|
|
- re.add(URIFlags.SEEK);
|
|
|
- }
|
|
|
- } catch(IOException cause) {
|
|
|
- LOGGER.warn("URI.flags: HTTP error", cause);
|
|
|
- }
|
|
|
- return re;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Instant time(URITime ut) throws IOException {
|
|
|
- if(ut == URITime.MODIFIED) {
|
|
|
- HttpURLConnection c = head();
|
|
|
- if(null == c.getHeaderField("Last-Modified")) {
|
|
|
- return Instant.ofEpochMilli(c.getDate());
|
|
|
- }
|
|
|
- return new Date(c.getHeaderFieldDate("Last-Modified", 0)).toInstant();
|
|
|
- }
|
|
|
- return super.time(ut);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public long size() throws IOException {
|
|
|
- HttpURLConnection c = head();
|
|
|
- if(2 != (c.getResponseCode() / 100)) {
|
|
|
- throw new IOException(c.getResponseMessage());
|
|
|
- }
|
|
|
- return c.getContentLengthLong();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public InputStream istream() throws IOException {
|
|
|
- return get.get().getInputStream();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public InputStream istream(long begin, long end) throws IOException {
|
|
|
- HttpURLConnection c = get.get();
|
|
|
- if(end == Long.MAX_VALUE) {
|
|
|
- c.setRequestProperty("Range", "bytes=" + begin + "-");
|
|
|
- } else {
|
|
|
- c.setRequestProperty("Range", "bytes=" + begin + "-" + end);
|
|
|
- }
|
|
|
- if (c.getResponseCode() != HttpURLConnection.HTTP_PARTIAL) {
|
|
|
- return IOStreams.limit(c.getInputStream(), begin, end);
|
|
|
- } else {
|
|
|
- return c.getInputStream();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public OutputStream ostream() throws IOException {
|
|
|
- HttpURLConnection c = post.get();
|
|
|
- c.connect();
|
|
|
- return c.getOutputStream();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void create() throws IOException {
|
|
|
- execute("PUT");
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void delete() throws IOException {
|
|
|
- execute("DELETE");
|
|
|
- }
|
|
|
-
|
|
|
- private HttpURLConnection head() throws IOException {
|
|
|
- return get.opened() ? get.get() : head.get();
|
|
|
- }
|
|
|
-
|
|
|
- private void execute(String method) throws IOException {
|
|
|
- HttpURLConnection c = connect(method);
|
|
|
- boolean success = isSuccess(c);
|
|
|
- c.disconnect();
|
|
|
- if(!success) {
|
|
|
- throw new IOException(c.getResponseCode() + ": " + c.getResponseMessage());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private HttpURLConnection connect(String method) throws IOException {
|
|
|
- HttpURLConnection c = (HttpURLConnection)uri.toURL().openConnection();
|
|
|
- c.setRequestMethod(method);
|
|
|
-
|
|
|
- CQuery<GenericMap<String>> ui = resolver.users().match(uri);
|
|
|
- if(!ui.isEmpty()) {
|
|
|
- setAuthorizationHeader(c, ui.first());
|
|
|
- }
|
|
|
- return c;
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private static boolean isSuccess(HttpURLConnection c) throws IOException {
|
|
|
- return 2 == (c.getResponseCode() / 100);
|
|
|
- }
|
|
|
-
|
|
|
- private static Charset getContentCharset(String type) throws IOException {
|
|
|
- if(null == type) {
|
|
|
- throw new IOException("Unknown MIME type");
|
|
|
- }
|
|
|
- if(!type.startsWith("text/")) {
|
|
|
- throw new IOException("Unsupported MIME type: " + type);
|
|
|
- }
|
|
|
- Matcher hit = RE_CHARSET.matcher(type);
|
|
|
- if(!hit.find()) {
|
|
|
- return Charsets.UTF8;
|
|
|
- }
|
|
|
- return Charset.forName(hit.group(1));
|
|
|
- }
|
|
|
-
|
|
|
- private static void setAuthorizationHeader(HttpURLConnection connection, GenericMap<String> user) {
|
|
|
- String passphrase = user.get(UserProperty.LOGIN) + ":" + user.get(UserProperty.PASSWORD);
|
|
|
- String header = "Basic " + DatatypeConverter.printBase64Binary(passphrase.getBytes());
|
|
|
- connection.setRequestProperty("Authorization", header);
|
|
|
- }
|
|
|
-}
|
|
|
+/*
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
+ * @license WTFPL
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
+ */
|
|
|
+package net.ranides.assira.io.uri.impl;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.OutputStream;
|
|
|
+import java.net.HttpURLConnection;
|
|
|
+import java.net.MalformedURLException;
|
|
|
+import java.net.URI;
|
|
|
+import java.nio.charset.Charset;
|
|
|
+import java.time.Instant;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+import javax.xml.bind.DatatypeConverter;
|
|
|
+import net.ranides.assira.collection.maps.GenericMap;
|
|
|
+import net.ranides.assira.collection.query.CQuery;
|
|
|
+import net.ranides.assira.credentials.UserProperty;
|
|
|
+import net.ranides.assira.io.IOHandle;
|
|
|
+import net.ranides.assira.io.IOStreams;
|
|
|
+import net.ranides.assira.io.uri.URITime;
|
|
|
+import net.ranides.assira.io.uri.URIFlags;
|
|
|
+import net.ranides.assira.io.uri.URIHandle;
|
|
|
+import net.ranides.assira.io.uri.URIHandler;
|
|
|
+import net.ranides.assira.io.uri.URIResolver;
|
|
|
+import net.ranides.assira.text.Charsets;
|
|
|
+import net.ranides.assira.text.StringTraits;
|
|
|
+import net.ranides.assira.trace.LoggerUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ */
|
|
|
+public class CHTTPHandler implements URIHandler {
|
|
|
+
|
|
|
+ private static final Logger LOGGER = LoggerUtils.getLogger();
|
|
|
+
|
|
|
+ private static final Pattern RE_CHARSET = Pattern.compile(";[ ]*charset=([^; ]+)");
|
|
|
+
|
|
|
+ private final URIResolver resolver;
|
|
|
+
|
|
|
+ public CHTTPHandler() {
|
|
|
+ this.resolver = URIResolver.DEFAULT;
|
|
|
+ }
|
|
|
+
|
|
|
+ public CHTTPHandler(URIResolver resolver) {
|
|
|
+ this.resolver = resolver;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public URIHandle resolve(URI uri) throws IOException {
|
|
|
+ return new CHttpHandle(uri);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Collection<String> schemes() {
|
|
|
+ return Arrays.asList("http","https");
|
|
|
+ }
|
|
|
+
|
|
|
+ private class CHttpHandle extends CHandle {
|
|
|
+
|
|
|
+ private final URI uri;
|
|
|
+ private final IOHandle<HttpURLConnection> head;
|
|
|
+ private final IOHandle<HttpURLConnection> get;
|
|
|
+ private final IOHandle<HttpURLConnection> post;
|
|
|
+
|
|
|
+ public CHttpHandle(URI uri) throws MalformedURLException {
|
|
|
+ this.uri = uri;
|
|
|
+ head = IOHandle.shared(() -> connect("HEAD"));
|
|
|
+ get = IOHandle.shared(() -> connect("GET"));
|
|
|
+ post = IOHandle.shared(() -> connect("POST"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public URIResolver resolver() {
|
|
|
+ return resolver;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public URI uri() throws IOException {
|
|
|
+ return uri;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Charset charset() throws IOException {
|
|
|
+ return getContentCharset(head().getContentType());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<URIFlags> flags() throws IOException {
|
|
|
+ Set<URIFlags> re = URIFlags.collect(URIFlags.FILE);
|
|
|
+ try {
|
|
|
+ HttpURLConnection c = head();
|
|
|
+ if(isSuccess(c)) {
|
|
|
+ re.add(URIFlags.EXISTS);
|
|
|
+ re.add(URIFlags.READABLE);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(StringTraits.starts(c.getContentType(), "text/")) {
|
|
|
+ re.add(URIFlags.TEXT);
|
|
|
+ } else {
|
|
|
+ re.add(URIFlags.BINARY);
|
|
|
+ }
|
|
|
+ if(c.getContentLengthLong() >= 0) {
|
|
|
+ re.add(URIFlags.SIZE);
|
|
|
+ }
|
|
|
+ if(StringTraits.contains(c.getHeaderField("Accept-Ranges"), "bytes")) {
|
|
|
+ re.add(URIFlags.SEEK);
|
|
|
+ }
|
|
|
+ } catch(IOException cause) {
|
|
|
+ LOGGER.warn("URI.flags: HTTP error", cause);
|
|
|
+ }
|
|
|
+ return re;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Instant time(URITime ut) throws IOException {
|
|
|
+ if(ut == URITime.MODIFIED) {
|
|
|
+ HttpURLConnection c = head();
|
|
|
+ if(null == c.getHeaderField("Last-Modified")) {
|
|
|
+ return Instant.ofEpochMilli(c.getDate());
|
|
|
+ }
|
|
|
+ return new Date(c.getHeaderFieldDate("Last-Modified", 0)).toInstant();
|
|
|
+ }
|
|
|
+ return super.time(ut);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public long size() throws IOException {
|
|
|
+ HttpURLConnection c = head();
|
|
|
+ if(2 != (c.getResponseCode() / 100)) {
|
|
|
+ throw new IOException(c.getResponseMessage());
|
|
|
+ }
|
|
|
+ return c.getContentLengthLong();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public InputStream istream() throws IOException {
|
|
|
+ return get.get().getInputStream();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public InputStream istream(long begin, long end) throws IOException {
|
|
|
+ HttpURLConnection c = get.get();
|
|
|
+ if(end == Long.MAX_VALUE) {
|
|
|
+ c.setRequestProperty("Range", "bytes=" + begin + "-");
|
|
|
+ } else {
|
|
|
+ c.setRequestProperty("Range", "bytes=" + begin + "-" + end);
|
|
|
+ }
|
|
|
+ if (c.getResponseCode() != HttpURLConnection.HTTP_PARTIAL) {
|
|
|
+ return IOStreams.limit(c.getInputStream(), begin, end);
|
|
|
+ } else {
|
|
|
+ return c.getInputStream();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public OutputStream ostream() throws IOException {
|
|
|
+ HttpURLConnection c = post.get();
|
|
|
+ c.connect();
|
|
|
+ return c.getOutputStream();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void create() throws IOException {
|
|
|
+ execute("PUT");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void delete() throws IOException {
|
|
|
+ execute("DELETE");
|
|
|
+ }
|
|
|
+
|
|
|
+ private HttpURLConnection head() throws IOException {
|
|
|
+ return get.opened() ? get.get() : head.get();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void execute(String method) throws IOException {
|
|
|
+ HttpURLConnection c = connect(method);
|
|
|
+ boolean success = isSuccess(c);
|
|
|
+ c.disconnect();
|
|
|
+ if(!success) {
|
|
|
+ throw new IOException(c.getResponseCode() + ": " + c.getResponseMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private HttpURLConnection connect(String method) throws IOException {
|
|
|
+ HttpURLConnection c = (HttpURLConnection)uri.toURL().openConnection();
|
|
|
+ c.setRequestMethod(method);
|
|
|
+
|
|
|
+ CQuery<GenericMap<String>> ui = resolver.users().match(uri);
|
|
|
+ if(!ui.isEmpty()) {
|
|
|
+ setAuthorizationHeader(c, ui.first());
|
|
|
+ }
|
|
|
+ return c;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static boolean isSuccess(HttpURLConnection c) throws IOException {
|
|
|
+ return 2 == (c.getResponseCode() / 100);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Charset getContentCharset(String type) throws IOException {
|
|
|
+ if(null == type) {
|
|
|
+ throw new IOException("Unknown MIME type");
|
|
|
+ }
|
|
|
+ if(!type.startsWith("text/")) {
|
|
|
+ throw new IOException("Unsupported MIME type: " + type);
|
|
|
+ }
|
|
|
+ Matcher hit = RE_CHARSET.matcher(type);
|
|
|
+ if(!hit.find()) {
|
|
|
+ return Charsets.UTF8;
|
|
|
+ }
|
|
|
+ return Charset.forName(hit.group(1));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void setAuthorizationHeader(HttpURLConnection connection, GenericMap<String> user) {
|
|
|
+ String passphrase = user.get(UserProperty.LOGIN) + ":" + user.get(UserProperty.PASSWORD);
|
|
|
+ String header = "Basic " + DatatypeConverter.printBase64Binary(passphrase.getBytes());
|
|
|
+ connection.setRequestProperty("Authorization", header);
|
|
|
+ }
|
|
|
+}
|