1 package de.juplo.httpresources;
2
3
4 import org.slf4j.Logger;
5 import org.slf4j.LoggerFactory;
6 import org.springframework.core.io.Resource;
7 import org.springframework.util.Assert;
8
9 import java.io.*;
10 import java.net.URI;
11 import java.net.URISyntaxException;
12 import java.time.Clock;
13 import java.util.regex.Pattern;
14
15
16
17
18
19
20 public class HttpResources
21 {
22 private final static Logger LOG =
23 LoggerFactory.getLogger(HttpResources.class);
24 private final static Pattern RESOURCE_PATH_PATTERN =
25 Pattern.compile("^http", Pattern.CASE_INSENSITIVE);
26 private final static URI ABSOLUTE_PATH = URI.create("/");
27
28
29 private final HttpResourceFetcher fetcher;
30 private final Clock clock;
31
32
33 public HttpResources(HttpResourceFetcher fetcher, Clock clock)
34 {
35 Assert.notNull(fetcher, "The HttpResourceFetcher must not be null");
36 Assert.notNull(clock, "The Clock must not be null");
37 this.fetcher = fetcher;
38 this.clock = clock;
39 }
40
41
42
43
44
45
46
47 public HttpResource getResource(String uri)
48 {
49 return getResource(HttpResources.convert(uri));
50 }
51
52
53
54
55
56
57
58
59 public HttpResource getResource(URI uri)
60 {
61 HttpData data = fetcher.fetch(uri, HttpData.NOT_FETCHED);
62 return new HttpResource(this, fetcher, clock, uri, data);
63 }
64
65
66 public static URI convert(String url)
67 {
68 return HttpResources.normalize(URI.create(url));
69 }
70
71 public static URI normalize(URI uri)
72 {
73 uri = uri.normalize();
74
75
76
77 if (uri.isOpaque())
78 throw new IllegalArgumentException("An opaque URI is no valid HTTP-URL: " + uri);
79
80 String scheme = null;
81 if (uri.isAbsolute())
82 {
83
84 switch (uri.getScheme().toLowerCase())
85 {
86 case "http":
87 scheme = "http";
88 break;
89 case "https":
90 scheme = "https";
91 break;
92 default:
93 throw new IllegalArgumentException("Unallowed scheme: " + uri);
94 }
95 }
96
97 String host = uri.getHost();
98 String path = uri.getRawPath();
99
100 if (host == null)
101 {
102 if (scheme != null)
103 throw new IllegalArgumentException("Host is missing, although scheme is not empty: " + scheme);
104 }
105 else
106 {
107 host = host.toLowerCase();
108 path = path == null || path.isEmpty() ? "/" : path;
109 }
110
111 try
112 {
113 return new URI(
114 scheme,
115 uri.getUserInfo(),
116 host,
117 uri.getPort(),
118 path,
119 uri.getQuery(),
120 uri.getFragment()
121 );
122 }
123 catch (URISyntaxException e)
124 {
125 throw new IllegalArgumentException("Invalid HTTP-URL: ", e);
126 }
127 }
128
129 public static URI resolve(URI relative, URI uri) throws IOException
130 {
131 if (relative.getAuthority() != null)
132 throw new IOException("URI is not relative: " + relative);
133
134 URI cleaned;
135 try
136 {
137 String path = relative.getRawPath();
138 int length = path == null ? -1 : path.length();
139 int i = 0;
140
141 while (i < length)
142 {
143 switch (path.charAt(i))
144 {
145 case '.':
146 case '/':
147 i++;
148 break;
149 default:
150 length = -1;
151 }
152 }
153
154 cleaned = i > 0
155 ? new URI(
156 null,
157 null,
158 null,
159 0,
160 i == path.length() ? null : path.substring(i),
161 relative.getQuery(),
162 relative.getFragment()
163 )
164 : relative;
165
166 path = uri.getPath();
167 if (path == null || path.length() == 0)
168 uri = uri.resolve(ABSOLUTE_PATH);
169 }
170 catch (URISyntaxException e)
171 {
172 throw new IOException("Invalid relative path: " + relative, e);
173 }
174
175 URI resolved = uri.resolve(cleaned);
176 LOG.trace("resolved {} as {} in context {}", relative, resolved, uri);
177 return resolved;
178 }
179
180
181 public static boolean isHttpResource(String resourcePath)
182 {
183 return RESOURCE_PATH_PATTERN.matcher(resourcePath).find();
184 }
185 }