├── .classpath
├── .project
├── .settings
└── org.eclipse.jdt.core.prefs
├── README.md
├── bin
├── Proxy.class
├── RequestHandler$ClientToServerHttpsTransmit.class
└── RequestHandler.class
└── src
├── Proxy.java
└── RequestHandler.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Proxy
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=1.8
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
11 | org.eclipse.jdt.core.compiler.source=1.8
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Java HTTP/HTTPS Proxy Server
2 | ## The Proxy Server
3 | A proxy server is a server that sits between the client and the remote server in which the client wishes to retrieve files from. All traffic that originates from the client, is sent to the proxy server and the proxy server makes requests to the remote server on the client’s behalf. Once the proxy server receives the required files, it then forwards them on to the client. This can be beneficial as it allows the proxy server administrator some control over what the machines on its network can do. For example, certain websites may be blocked by the proxy server, meaning clients will not be able to access them. It is also beneficial as frequently visited pages can be cached by the proxy server. This means that when the client (or other clients) make subsequent requests for any files that have been cached, the proxy can issue them the files straight away, without having to request them from the remote server which can be much quicker if both the proxy and the clients are on the same network. Although these files are known to be contained in the proxy’s cache, it is worth noting that the clients have no knowledge of this and may be maintaining their own local caches. The benefit of the proxy cache is when multiple clients are using the proxy and thus pages cached due to one client can be accessed by another client.
4 |
5 | ## The Implementation
6 | The proxy was implemented using Java and made extensive use of TCP sockets. Firefox was set up to issue all of its traffic to the specified port and ip address which were then used in the proxy configuration. There are two main components to the implementation - the Proxy class and the RequestHandler class.
7 | The Proxy Class
8 | The Proxy class is responsible for creating a ServerSocket which can accept incoming socket connections from the client. However it is vital that the implementation be multithreaded as the server must be able to serve multiple clients simultaneously. Thus once a socket connection arrives, it is accepted and the Proxy creates a new thread which services the request (see the RequestHandler class). As the server does not need to wait for the request to be fully serviced before accepting a new socket connection, multiple clients may have their requests serviced asynchronously.
9 |
10 | The Proxy class is also responsible for implementing caching and blocking functionality. That is, the proxy is able to cache sites that are requested by clients and dynamically block clients from visiting certain websites. As speed is of utmost importance for the proxy server, it is desirable to store references to currently blocked sites and sites that are contained in the cache in a data structure with an expected constant order lookup time. For this reason a HashMap was chosen. This results in extremely fast cache and blocked site lookup times. This results in only a small overhead if the file is not contained in the cache, and an increase in performance if the file was contained in the cache.
11 | Finally the proxy class is also responsible for the providing a dynamic console management system. This allows an administrator to add/remove files to and from the cache and websites to and from the blacklist, in real time.
12 |
13 | ## The RequestHandler Class
14 | The RequestHandler class is responsible for servicing the requests that come through to the proxy. The RequestHandler examines the request received and services the request appropriately. The requests can be subdivided into three main categories - HTTP GET requests, HTTP GET requests for file contained in the cache and HTTPS CONNECT requests.
15 | ### HTTP GET
16 | These are the standard requests made when a client attempts to load a webpage. Servicing these requests is a simple task:
17 | -Parse out the URL associated with the request.
18 | -Create a HTTP connection to this URL.
19 | -Echo the client’s GET request to the remote servr.
20 | -Echo the server’s response back to the cliet.
21 | -Save a local copy of the file into the proxy’s cache.
22 | ### HTTP GET for File in Cache
23 | As before, these are the typical requests made by clients, only in this case, the file is contained in the proxy’s cache.
24 | -Parse out the URL associated with the request
25 | -Hash the URL and use this as the key for the HashMap data structure.
26 | -Open the resulting file for reading.
27 | -Echo the contents of the file back to the client.
28 | -Close the file.
29 | ### HTTPS - CONNECT Requests
30 | HTTPS connections make use of secure sockets (SSL). Data transferred between the client and the server is encrypted. This is widely used in the financial sector in order to ensure secure transactions, but is becoming increasingly more widespread on the internet.
31 | However at first glance it poses a problem for proxy servers: How is the proxy to know what to do with this encrypted data coming from the client?
32 | In order to overcome this problem, initially, another type of HTTP request is made by the client, a CONNECT request. This request is standard HTTP and thus is unencrypted and contains the address of who the client wants to create a HTTPS connection with and this can be extracted by the proxy. This is a process known as HTTP Connect Tunneling and works as follows:
33 | -Client issues a CONNECT Request
34 | -Proxy extracts the destination URL.
35 | -Proxy creates a standard socket connection to the remote server specified by the URL.
36 | -If successful, the proxy sends a ‘200 Connection Established ‘ response to the client, indicating that the client can now begin to transmit the encrypted data to the proxy.
37 | -The proxy then simultaneously forwards any data sent to it from the client to the remote server, and any data received from the remote server back to the client.
38 |
39 | All of this data will be encrypted and thus the proxy cannot cache or even interpret the data.
40 |
--------------------------------------------------------------------------------
/bin/Proxy.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefano-lupo/Java-Proxy-Server/2d424a400b6df762461dd690bbca7205c1e8c7f2/bin/Proxy.class
--------------------------------------------------------------------------------
/bin/RequestHandler$ClientToServerHttpsTransmit.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefano-lupo/Java-Proxy-Server/2d424a400b6df762461dd690bbca7205c1e8c7f2/bin/RequestHandler$ClientToServerHttpsTransmit.class
--------------------------------------------------------------------------------
/bin/RequestHandler.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefano-lupo/Java-Proxy-Server/2d424a400b6df762461dd690bbca7205c1e8c7f2/bin/RequestHandler.class
--------------------------------------------------------------------------------
/src/Proxy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Student: Stefano Lupo
3 | * Student No: 14334933
4 | * Degree: JS Computer Engineering
5 | * Course: 3D3 Computer Networks
6 | * Date: 02/04/2017
7 | */
8 |
9 | import java.io.File;
10 | import java.io.FileInputStream;
11 | import java.io.FileOutputStream;
12 | import java.io.IOException;
13 | import java.io.ObjectInputStream;
14 | import java.io.ObjectOutputStream;
15 | import java.net.ServerSocket;
16 | import java.net.Socket;
17 | import java.net.SocketException;
18 | import java.net.SocketTimeoutException;
19 | import java.util.ArrayList;
20 | import java.util.HashMap;
21 | import java.util.Scanner;
22 |
23 |
24 | /**
25 | * The Proxy creates a Server Socket which will wait for connections on the specified port.
26 | * Once a connection arrives and a socket is accepted, the Proxy creates a RequestHandler object
27 | * on a new thread and passes the socket to it to be handled.
28 | * This allows the Proxy to continue accept further connections while others are being handled.
29 | *
30 | * The Proxy class is also responsible for providing the dynamic management of the proxy through the console
31 | * and is run on a separate thread in order to not interrupt the acceptance of socket connections.
32 | * This allows the administrator to dynamically block web sites in real time.
33 | *
34 | * The Proxy server is also responsible for maintaining cached copies of the any websites that are requested by
35 | * clients and this includes the HTML markup, images, css and js files associated with each webpage.
36 | *
37 | * Upon closing the proxy server, the HashMaps which hold cached items and blocked sites are serialized and
38 | * written to a file and are loaded back in when the proxy is started once more, meaning that cached and blocked
39 | * sites are maintained.
40 | *
41 | */
42 | public class Proxy implements Runnable{
43 |
44 |
45 | // Main method for the program
46 | public static void main(String[] args) {
47 | // Create an instance of Proxy and begin listening for connections
48 | Proxy myProxy = new Proxy(8085);
49 | myProxy.listen();
50 | }
51 |
52 |
53 | private ServerSocket serverSocket;
54 |
55 | /**
56 | * Semaphore for Proxy and Consolee Management System.
57 | */
58 | private volatile boolean running = true;
59 |
60 |
61 | /**
62 | * Data structure for constant order lookup of cache items.
63 | * Key: URL of page/image requested.
64 | * Value: File in storage associated with this key.
65 | */
66 | static HashMap cache;
67 |
68 | /**
69 | * Data structure for constant order lookup of blocked sites.
70 | * Key: URL of page/image requested.
71 | * Value: URL of page/image requested.
72 | */
73 | static HashMap blockedSites;
74 |
75 | /**
76 | * ArrayList of threads that are currently running and servicing requests.
77 | * This list is required in order to join all threads on closing of server
78 | */
79 | static ArrayList servicingThreads;
80 |
81 |
82 |
83 | /**
84 | * Create the Proxy Server
85 | * @param port Port number to run proxy server from.
86 | */
87 | public Proxy(int port) {
88 |
89 | // Load in hash map containing previously cached sites and blocked Sites
90 | cache = new HashMap<>();
91 | blockedSites = new HashMap<>();
92 |
93 | // Create array list to hold servicing threads
94 | servicingThreads = new ArrayList<>();
95 |
96 | // Start dynamic manager on a separate thread.
97 | new Thread(this).start(); // Starts overriden run() method at bottom
98 |
99 | try{
100 | // Load in cached sites from file
101 | File cachedSites = new File("cachedSites.txt");
102 | if(!cachedSites.exists()){
103 | System.out.println("No cached sites found - creating new file");
104 | cachedSites.createNewFile();
105 | } else {
106 | FileInputStream fileInputStream = new FileInputStream(cachedSites);
107 | ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
108 | cache = (HashMap)objectInputStream.readObject();
109 | fileInputStream.close();
110 | objectInputStream.close();
111 | }
112 |
113 | // Load in blocked sites from file
114 | File blockedSitesTxtFile = new File("blockedSites.txt");
115 | if(!blockedSitesTxtFile.exists()){
116 | System.out.println("No blocked sites found - creating new file");
117 | blockedSitesTxtFile.createNewFile();
118 | } else {
119 | FileInputStream fileInputStream = new FileInputStream(blockedSitesTxtFile);
120 | ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
121 | blockedSites = (HashMap)objectInputStream.readObject();
122 | fileInputStream.close();
123 | objectInputStream.close();
124 | }
125 | } catch (IOException e) {
126 | System.out.println("Error loading previously cached sites file");
127 | e.printStackTrace();
128 | } catch (ClassNotFoundException e) {
129 | System.out.println("Class not found loading in preivously cached sites file");
130 | e.printStackTrace();
131 | }
132 |
133 | try {
134 | // Create the Server Socket for the Proxy
135 | serverSocket = new ServerSocket(port);
136 |
137 | // Set the timeout
138 | //serverSocket.setSoTimeout(100000); // debug
139 | System.out.println("Waiting for client on port " + serverSocket.getLocalPort() + "..");
140 | running = true;
141 | }
142 |
143 | // Catch exceptions associated with opening socket
144 | catch (SocketException se) {
145 | System.out.println("Socket Exception when connecting to client");
146 | se.printStackTrace();
147 | }
148 | catch (SocketTimeoutException ste) {
149 | System.out.println("Timeout occured while connecting to client");
150 | }
151 | catch (IOException io) {
152 | System.out.println("IO exception when connecting to client");
153 | }
154 | }
155 |
156 |
157 | /**
158 | * Listens to port and accepts new socket connections.
159 | * Creates a new thread to handle the request and passes it the socket connection and continues listening.
160 | */
161 | public void listen(){
162 |
163 | while(running){
164 | try {
165 | // serverSocket.accpet() Blocks until a connection is made
166 | Socket socket = serverSocket.accept();
167 |
168 | // Create new Thread and pass it Runnable RequestHandler
169 | Thread thread = new Thread(new RequestHandler(socket));
170 |
171 | // Key a reference to each thread so they can be joined later if necessary
172 | servicingThreads.add(thread);
173 |
174 | thread.start();
175 | } catch (SocketException e) {
176 | // Socket exception is triggered by management system to shut down the proxy
177 | System.out.println("Server closed");
178 | } catch (IOException e) {
179 | e.printStackTrace();
180 | }
181 | }
182 | }
183 |
184 |
185 | /**
186 | * Saves the blocked and cached sites to a file so they can be re loaded at a later time.
187 | * Also joins all of the RequestHandler threads currently servicing requests.
188 | */
189 | private void closeServer(){
190 | System.out.println("\nClosing Server..");
191 | running = false;
192 | try{
193 | FileOutputStream fileOutputStream = new FileOutputStream("cachedSites.txt");
194 | ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
195 |
196 | objectOutputStream.writeObject(cache);
197 | objectOutputStream.close();
198 | fileOutputStream.close();
199 | System.out.println("Cached Sites written");
200 |
201 | FileOutputStream fileOutputStream2 = new FileOutputStream("blockedSites.txt");
202 | ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(fileOutputStream2);
203 | objectOutputStream2.writeObject(blockedSites);
204 | objectOutputStream2.close();
205 | fileOutputStream2.close();
206 | System.out.println("Blocked Site list saved");
207 | try{
208 | // Close all servicing threads
209 | for(Thread thread : servicingThreads){
210 | if(thread.isAlive()){
211 | System.out.print("Waiting on "+ thread.getId()+" to close..");
212 | thread.join();
213 | System.out.println(" closed");
214 | }
215 | }
216 | } catch (InterruptedException e) {
217 | e.printStackTrace();
218 | }
219 |
220 | } catch (IOException e) {
221 | System.out.println("Error saving cache/blocked sites");
222 | e.printStackTrace();
223 | }
224 |
225 | // Close Server Socket
226 | try{
227 | System.out.println("Terminating Connection");
228 | serverSocket.close();
229 | } catch (Exception e) {
230 | System.out.println("Exception closing proxy's server socket");
231 | e.printStackTrace();
232 | }
233 |
234 | }
235 |
236 |
237 | /**
238 | * Looks for File in cache
239 | * @param url of requested file
240 | * @return File if file is cached, null otherwise
241 | */
242 | public static File getCachedPage(String url){
243 | return cache.get(url);
244 | }
245 |
246 |
247 | /**
248 | * Adds a new page to the cache
249 | * @param urlString URL of webpage to cache
250 | * @param fileToCache File Object pointing to File put in cache
251 | */
252 | public static void addCachedPage(String urlString, File fileToCache){
253 | cache.put(urlString, fileToCache);
254 | }
255 |
256 | /**
257 | * Check if a URL is blocked by the proxy
258 | * @param url URL to check
259 | * @return true if URL is blocked, false otherwise
260 | */
261 | public static boolean isBlocked (String url){
262 | if(blockedSites.get(url) != null){
263 | return true;
264 | } else {
265 | return false;
266 | }
267 | }
268 |
269 |
270 |
271 |
272 | /**
273 | * Creates a management interface which can dynamically update the proxy configurations
274 | * blocked : Lists currently blocked sites
275 | * cached : Lists currently cached sites
276 | * close : Closes the proxy server
277 | * * : Adds * to the list of blocked sites
278 | */
279 | @Override
280 | public void run() {
281 | Scanner scanner = new Scanner(System.in);
282 |
283 | String command;
284 | while(running){
285 | System.out.println("Enter new site to block, or type \"blocked\" to see blocked sites, \"cached\" to see cached sites, or \"close\" to close server.");
286 | command = scanner.nextLine();
287 | if(command.toLowerCase().equals("blocked")){
288 | System.out.println("\nCurrently Blocked Sites");
289 | for(String key : blockedSites.keySet()){
290 | System.out.println(key);
291 | }
292 | System.out.println();
293 | }
294 |
295 | else if(command.toLowerCase().equals("cached")){
296 | System.out.println("\nCurrently Cached Sites");
297 | for(String key : cache.keySet()){
298 | System.out.println(key);
299 | }
300 | System.out.println();
301 | }
302 |
303 |
304 | else if(command.equals("close")){
305 | running = false;
306 | closeServer();
307 | }
308 |
309 |
310 | else {
311 | blockedSites.put(command, command);
312 | System.out.println("\n" + command + " blocked successfully \n");
313 | }
314 | }
315 | scanner.close();
316 | }
317 |
318 | }
319 |
--------------------------------------------------------------------------------
/src/RequestHandler.java:
--------------------------------------------------------------------------------
1 | import java.awt.image.BufferedImage;
2 | import java.io.BufferedReader;
3 | import java.io.BufferedWriter;
4 | import java.io.File;
5 | import java.io.FileInputStream;
6 | import java.io.FileWriter;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.io.InputStreamReader;
10 | import java.io.OutputStream;
11 | import java.io.OutputStreamWriter;
12 | import java.net.HttpURLConnection;
13 | import java.net.InetAddress;
14 | import java.net.Socket;
15 | import java.net.SocketTimeoutException;
16 | import java.net.URL;
17 | import javax.imageio.ImageIO;
18 |
19 | public class RequestHandler implements Runnable {
20 |
21 | /**
22 | * Socket connected to client passed by Proxy server
23 | */
24 | Socket clientSocket;
25 |
26 | /**
27 | * Read data client sends to proxy
28 | */
29 | BufferedReader proxyToClientBr;
30 |
31 | /**
32 | * Send data from proxy to client
33 | */
34 | BufferedWriter proxyToClientBw;
35 |
36 |
37 | /**
38 | * Thread that is used to transmit data read from client to server when using HTTPS
39 | * Reference to this is required so it can be closed once completed.
40 | */
41 | private Thread httpsClientToServer;
42 |
43 |
44 | /**
45 | * Creates a ReuqestHandler object capable of servicing HTTP(S) GET requests
46 | * @param clientSocket socket connected to the client
47 | */
48 | public RequestHandler(Socket clientSocket){
49 | this.clientSocket = clientSocket;
50 | try{
51 | this.clientSocket.setSoTimeout(2000);
52 | proxyToClientBr = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
53 | proxyToClientBw = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
54 | }
55 | catch (IOException e) {
56 | e.printStackTrace();
57 | }
58 | }
59 |
60 |
61 |
62 | /**
63 | * Reads and examines the requestString and calls the appropriate method based
64 | * on the request type.
65 | */
66 | @Override
67 | public void run() {
68 |
69 | // Get Request from client
70 | String requestString;
71 | try{
72 | requestString = proxyToClientBr.readLine();
73 | } catch (IOException e) {
74 | e.printStackTrace();
75 | System.out.println("Error reading request from client");
76 | return;
77 | }
78 |
79 | // Parse out URL
80 |
81 | System.out.println("Reuest Received " + requestString);
82 | // Get the Request type
83 | String request = requestString.substring(0,requestString.indexOf(' '));
84 |
85 | // remove request type and space
86 | String urlString = requestString.substring(requestString.indexOf(' ')+1);
87 |
88 | // Remove everything past next space
89 | urlString = urlString.substring(0, urlString.indexOf(' '));
90 |
91 | // Prepend http:// if necessary to create correct URL
92 | if(!urlString.substring(0,4).equals("http")){
93 | String temp = "http://";
94 | urlString = temp + urlString;
95 | }
96 |
97 |
98 | // Check if site is blocked
99 | if(Proxy.isBlocked(urlString)){
100 | System.out.println("Blocked site requested : " + urlString);
101 | blockedSiteRequested();
102 | return;
103 | }
104 |
105 |
106 | // Check request type
107 | if(request.equals("CONNECT")){
108 | System.out.println("HTTPS Request for : " + urlString + "\n");
109 | handleHTTPSRequest(urlString);
110 | }
111 |
112 | else{
113 | // Check if we have a cached copy
114 | File file;
115 | if((file = Proxy.getCachedPage(urlString)) != null){
116 | System.out.println("Cached Copy found for : " + urlString + "\n");
117 | sendCachedPageToClient(file);
118 | } else {
119 | System.out.println("HTTP GET for : " + urlString + "\n");
120 | sendNonCachedToClient(urlString);
121 | }
122 | }
123 | }
124 |
125 |
126 | /**
127 | * Sends the specified cached file to the client
128 | * @param cachedFile The file to be sent (can be image/text)
129 | */
130 | private void sendCachedPageToClient(File cachedFile){
131 | // Read from File containing cached web page
132 | try{
133 | // If file is an image write data to client using buffered image.
134 | String fileExtension = cachedFile.getName().substring(cachedFile.getName().lastIndexOf('.'));
135 |
136 | // Response that will be sent to the server
137 | String response;
138 | if((fileExtension.contains(".png")) || fileExtension.contains(".jpg") ||
139 | fileExtension.contains(".jpeg") || fileExtension.contains(".gif")){
140 | // Read in image from storage
141 | BufferedImage image = ImageIO.read(cachedFile);
142 |
143 | if(image == null ){
144 | System.out.println("Image " + cachedFile.getName() + " was null");
145 | response = "HTTP/1.0 404 NOT FOUND \n" +
146 | "Proxy-agent: ProxyServer/1.0\n" +
147 | "\r\n";
148 | proxyToClientBw.write(response);
149 | proxyToClientBw.flush();
150 | } else {
151 | response = "HTTP/1.0 200 OK\n" +
152 | "Proxy-agent: ProxyServer/1.0\n" +
153 | "\r\n";
154 | proxyToClientBw.write(response);
155 | proxyToClientBw.flush();
156 | ImageIO.write(image, fileExtension.substring(1), clientSocket.getOutputStream());
157 | }
158 | }
159 |
160 | // Standard text based file requested
161 | else {
162 | BufferedReader cachedFileBufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(cachedFile)));
163 |
164 | response = "HTTP/1.0 200 OK\n" +
165 | "Proxy-agent: ProxyServer/1.0\n" +
166 | "\r\n";
167 | proxyToClientBw.write(response);
168 | proxyToClientBw.flush();
169 |
170 | String line;
171 | while((line = cachedFileBufferedReader.readLine()) != null){
172 | proxyToClientBw.write(line);
173 | }
174 | proxyToClientBw.flush();
175 |
176 | // Close resources
177 | if(cachedFileBufferedReader != null){
178 | cachedFileBufferedReader.close();
179 | }
180 | }
181 |
182 |
183 | // Close Down Resources
184 | if(proxyToClientBw != null){
185 | proxyToClientBw.close();
186 | }
187 |
188 | } catch (IOException e) {
189 | System.out.println("Error Sending Cached file to client");
190 | e.printStackTrace();
191 | }
192 | }
193 |
194 |
195 | /**
196 | * Sends the contents of the file specified by the urlString to the client
197 | * @param urlString URL ofthe file requested
198 | */
199 | private void sendNonCachedToClient(String urlString){
200 |
201 | try{
202 |
203 | // Compute a logical file name as per schema
204 | // This allows the files on stored on disk to resemble that of the URL it was taken from
205 | int fileExtensionIndex = urlString.lastIndexOf(".");
206 | String fileExtension;
207 |
208 | // Get the type of file
209 | fileExtension = urlString.substring(fileExtensionIndex, urlString.length());
210 |
211 | // Get the initial file name
212 | String fileName = urlString.substring(0,fileExtensionIndex);
213 |
214 |
215 | // Trim off http://www. as no need for it in file name
216 | fileName = fileName.substring(fileName.indexOf('.')+1);
217 |
218 | // Remove any illegal characters from file name
219 | fileName = fileName.replace("/", "__");
220 | fileName = fileName.replace('.','_');
221 |
222 | // Trailing / result in index.html of that directory being fetched
223 | if(fileExtension.contains("/")){
224 | fileExtension = fileExtension.replace("/", "__");
225 | fileExtension = fileExtension.replace('.','_');
226 | fileExtension += ".html";
227 | }
228 |
229 | fileName = fileName + fileExtension;
230 |
231 |
232 |
233 | // Attempt to create File to cache to
234 | boolean caching = true;
235 | File fileToCache = null;
236 | BufferedWriter fileToCacheBW = null;
237 |
238 | try{
239 | // Create File to cache
240 | fileToCache = new File("cached/" + fileName);
241 |
242 | if(!fileToCache.exists()){
243 | fileToCache.createNewFile();
244 | }
245 |
246 | // Create Buffered output stream to write to cached copy of file
247 | fileToCacheBW = new BufferedWriter(new FileWriter(fileToCache));
248 | }
249 | catch (IOException e){
250 | System.out.println("Couldn't cache: " + fileName);
251 | caching = false;
252 | e.printStackTrace();
253 | } catch (NullPointerException e) {
254 | System.out.println("NPE opening file");
255 | }
256 |
257 |
258 |
259 |
260 |
261 | // Check if file is an image
262 | if((fileExtension.contains(".png")) || fileExtension.contains(".jpg") ||
263 | fileExtension.contains(".jpeg") || fileExtension.contains(".gif")){
264 | // Create the URL
265 | URL remoteURL = new URL(urlString);
266 | BufferedImage image = ImageIO.read(remoteURL);
267 |
268 | if(image != null) {
269 | // Cache the image to disk
270 | ImageIO.write(image, fileExtension.substring(1), fileToCache);
271 |
272 | // Send response code to client
273 | String line = "HTTP/1.0 200 OK\n" +
274 | "Proxy-agent: ProxyServer/1.0\n" +
275 | "\r\n";
276 | proxyToClientBw.write(line);
277 | proxyToClientBw.flush();
278 |
279 | // Send them the image data
280 | ImageIO.write(image, fileExtension.substring(1), clientSocket.getOutputStream());
281 |
282 | // No image received from remote server
283 | } else {
284 | System.out.println("Sending 404 to client as image wasn't received from server"
285 | + fileName);
286 | String error = "HTTP/1.0 404 NOT FOUND\n" +
287 | "Proxy-agent: ProxyServer/1.0\n" +
288 | "\r\n";
289 | proxyToClientBw.write(error);
290 | proxyToClientBw.flush();
291 | return;
292 | }
293 | }
294 |
295 | // File is a text file
296 | else {
297 |
298 | // Create the URL
299 | URL remoteURL = new URL(urlString);
300 | // Create a connection to remote server
301 | HttpURLConnection proxyToServerCon = (HttpURLConnection)remoteURL.openConnection();
302 | proxyToServerCon.setRequestProperty("Content-Type",
303 | "application/x-www-form-urlencoded");
304 | proxyToServerCon.setRequestProperty("Content-Language", "en-US");
305 | proxyToServerCon.setUseCaches(false);
306 | proxyToServerCon.setDoOutput(true);
307 |
308 | // Create Buffered Reader from remote Server
309 | BufferedReader proxyToServerBR = new BufferedReader(new InputStreamReader(proxyToServerCon.getInputStream()));
310 |
311 |
312 | // Send success code to client
313 | String line = "HTTP/1.0 200 OK\n" +
314 | "Proxy-agent: ProxyServer/1.0\n" +
315 | "\r\n";
316 | proxyToClientBw.write(line);
317 |
318 |
319 | // Read from input stream between proxy and remote server
320 | while((line = proxyToServerBR.readLine()) != null){
321 | // Send on data to client
322 | proxyToClientBw.write(line);
323 |
324 | // Write to our cached copy of the file
325 | if(caching){
326 | fileToCacheBW.write(line);
327 | }
328 | }
329 |
330 | // Ensure all data is sent by this point
331 | proxyToClientBw.flush();
332 |
333 | // Close Down Resources
334 | if(proxyToServerBR != null){
335 | proxyToServerBR.close();
336 | }
337 | }
338 |
339 |
340 | if(caching){
341 | // Ensure data written and add to our cached hash maps
342 | fileToCacheBW.flush();
343 | Proxy.addCachedPage(urlString, fileToCache);
344 | }
345 |
346 | // Close down resources
347 | if(fileToCacheBW != null){
348 | fileToCacheBW.close();
349 | }
350 |
351 | if(proxyToClientBw != null){
352 | proxyToClientBw.close();
353 | }
354 | }
355 |
356 | catch (Exception e){
357 | e.printStackTrace();
358 | }
359 | }
360 |
361 |
362 | /**
363 | * Handles HTTPS requests between client and remote server
364 | * @param urlString desired file to be transmitted over https
365 | */
366 | private void handleHTTPSRequest(String urlString){
367 | // Extract the URL and port of remote
368 | String url = urlString.substring(7);
369 | String pieces[] = url.split(":");
370 | url = pieces[0];
371 | int port = Integer.valueOf(pieces[1]);
372 |
373 | try{
374 | // Only first line of HTTPS request has been read at this point (CONNECT *)
375 | // Read (and throw away) the rest of the initial data on the stream
376 | for(int i=0;i<5;i++){
377 | proxyToClientBr.readLine();
378 | }
379 |
380 | // Get actual IP associated with this URL through DNS
381 | InetAddress address = InetAddress.getByName(url);
382 |
383 | // Open a socket to the remote server
384 | Socket proxyToServerSocket = new Socket(address, port);
385 | proxyToServerSocket.setSoTimeout(5000);
386 |
387 | // Send Connection established to the client
388 | String line = "HTTP/1.0 200 Connection established\r\n" +
389 | "Proxy-Agent: ProxyServer/1.0\r\n" +
390 | "\r\n";
391 | proxyToClientBw.write(line);
392 | proxyToClientBw.flush();
393 |
394 |
395 |
396 | // Client and Remote will both start sending data to proxy at this point
397 | // Proxy needs to asynchronously read data from each party and send it to the other party
398 |
399 |
400 | //Create a Buffered Writer betwen proxy and remote
401 | BufferedWriter proxyToServerBW = new BufferedWriter(new OutputStreamWriter(proxyToServerSocket.getOutputStream()));
402 |
403 | // Create Buffered Reader from proxy and remote
404 | BufferedReader proxyToServerBR = new BufferedReader(new InputStreamReader(proxyToServerSocket.getInputStream()));
405 |
406 |
407 |
408 | // Create a new thread to listen to client and transmit to server
409 | ClientToServerHttpsTransmit clientToServerHttps =
410 | new ClientToServerHttpsTransmit(clientSocket.getInputStream(), proxyToServerSocket.getOutputStream());
411 |
412 | httpsClientToServer = new Thread(clientToServerHttps);
413 | httpsClientToServer.start();
414 |
415 |
416 | // Listen to remote server and relay to client
417 | try {
418 | byte[] buffer = new byte[4096];
419 | int read;
420 | do {
421 | read = proxyToServerSocket.getInputStream().read(buffer);
422 | if (read > 0) {
423 | clientSocket.getOutputStream().write(buffer, 0, read);
424 | if (proxyToServerSocket.getInputStream().available() < 1) {
425 | clientSocket.getOutputStream().flush();
426 | }
427 | }
428 | } while (read >= 0);
429 | }
430 | catch (SocketTimeoutException e) {
431 |
432 | }
433 | catch (IOException e) {
434 | e.printStackTrace();
435 | }
436 |
437 |
438 | // Close Down Resources
439 | if(proxyToServerSocket != null){
440 | proxyToServerSocket.close();
441 | }
442 |
443 | if(proxyToServerBR != null){
444 | proxyToServerBR.close();
445 | }
446 |
447 | if(proxyToServerBW != null){
448 | proxyToServerBW.close();
449 | }
450 |
451 | if(proxyToClientBw != null){
452 | proxyToClientBw.close();
453 | }
454 |
455 |
456 | } catch (SocketTimeoutException e) {
457 | String line = "HTTP/1.0 504 Timeout Occured after 10s\n" +
458 | "User-Agent: ProxyServer/1.0\n" +
459 | "\r\n";
460 | try{
461 | proxyToClientBw.write(line);
462 | proxyToClientBw.flush();
463 | } catch (IOException ioe) {
464 | ioe.printStackTrace();
465 | }
466 | }
467 | catch (Exception e){
468 | System.out.println("Error on HTTPS : " + urlString );
469 | e.printStackTrace();
470 | }
471 | }
472 |
473 |
474 |
475 |
476 | /**
477 | * Listen to data from client and transmits it to server.
478 | * This is done on a separate thread as must be done
479 | * asynchronously to reading data from server and transmitting
480 | * that data to the client.
481 | */
482 | class ClientToServerHttpsTransmit implements Runnable{
483 |
484 | InputStream proxyToClientIS;
485 | OutputStream proxyToServerOS;
486 |
487 | /**
488 | * Creates Object to Listen to Client and Transmit that data to the server
489 | * @param proxyToClientIS Stream that proxy uses to receive data from client
490 | * @param proxyToServerOS Stream that proxy uses to transmit data to remote server
491 | */
492 | public ClientToServerHttpsTransmit(InputStream proxyToClientIS, OutputStream proxyToServerOS) {
493 | this.proxyToClientIS = proxyToClientIS;
494 | this.proxyToServerOS = proxyToServerOS;
495 | }
496 |
497 | @Override
498 | public void run(){
499 | try {
500 | // Read byte by byte from client and send directly to server
501 | byte[] buffer = new byte[4096];
502 | int read;
503 | do {
504 | read = proxyToClientIS.read(buffer);
505 | if (read > 0) {
506 | proxyToServerOS.write(buffer, 0, read);
507 | if (proxyToClientIS.available() < 1) {
508 | proxyToServerOS.flush();
509 | }
510 | }
511 | } while (read >= 0);
512 | }
513 | catch (SocketTimeoutException ste) {
514 | // TODO: handle exception
515 | }
516 | catch (IOException e) {
517 | System.out.println("Proxy to client HTTPS read timed out");
518 | e.printStackTrace();
519 | }
520 | }
521 | }
522 |
523 |
524 | /**
525 | * This method is called when user requests a page that is blocked by the proxy.
526 | * Sends an access forbidden message back to the client
527 | */
528 | private void blockedSiteRequested(){
529 | try {
530 | BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
531 | String line = "HTTP/1.0 403 Access Forbidden \n" +
532 | "User-Agent: ProxyServer/1.0\n" +
533 | "\r\n";
534 | bufferedWriter.write(line);
535 | bufferedWriter.flush();
536 | } catch (IOException e) {
537 | System.out.println("Error writing to client when requested a blocked site");
538 | e.printStackTrace();
539 | }
540 | }
541 | }
542 |
543 |
544 |
545 |
546 |
--------------------------------------------------------------------------------