View Javadoc
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   * @author Kai Moritz
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     * @param uri the resource location (must be a valid <code>URI</code>)
44     * @return the corresponding Resource handle (never {@code null})
45     * @see {@link HttpResources#getResource(String)}
46     */
47    public HttpResource getResource(String uri)
48    {
49      return getResource(HttpResources.convert(uri));
50    }
51  
52    /**
53     * Returns the {@link HttpResource}, that represents the given {@link URI}.
54  	 * <p>Note that a Resource handle does not imply an existing resource;
55  	 * you need to invoke {@link Resource#exists} to check for existence.
56  	 * @param uri the resource location, represented as an {@link URI}
57  	 * @return the corresponding Resource handle (never {@code null})
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      // An URI is opaque, if it is absolute and the scheme-specific part does
76      // not start with a slash. A HTTP-URL cannot be opaque in this meaning!
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        // Enforce scheme "HTTP" or "HTTPS"
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 }