source: 3thparty/jupload/src/main/java/wjhk/jupload2/upload/helper/HTTPConnectionHelper.java @ 3951

Revision 3951, 30.9 KB checked in by alexandrecorreia, 13 years ago (diff)

Ticket #1709 - Adicao de codigo fonte java do componente jupload

Line 
1//
2// $Id: FileUploadThreadHTTP.java 488 2008-07-06 20:21:43Z etienne_sf $
3//
4// jupload - A file upload applet.
5// Copyright 2007 The JUpload Team
6//
7// Created: 2007-03-07
8// Creator: etienne_sf
9// Last modified: $Date: 2008-07-06 22:21:43 +0200 (dim., 06 juil. 2008) $
10//
11// This program is free software; you can redistribute it and/or modify it under
12// the terms of the GNU General Public License as published by the Free Software
13// Foundation; either version 2 of the License, or (at your option) any later
14// version. This program is distributed in the hope that it will be useful, but
15// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17// details. You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software Foundation, Inc.,
19// 675 Mass Ave, Cambridge, MA 02139, USA.
20
21package wjhk.jupload2.upload.helper;
22
23import java.io.BufferedOutputStream;
24import java.io.DataOutputStream;
25import java.io.IOException;
26import java.io.OutputStream;
27import java.io.PushbackInputStream;
28import java.net.Proxy;
29import java.net.ProxySelector;
30import java.net.Socket;
31import java.net.URISyntaxException;
32import java.net.URL;
33import java.security.KeyManagementException;
34import java.security.KeyStoreException;
35import java.security.NoSuchAlgorithmException;
36import java.security.UnrecoverableKeyException;
37import java.security.cert.CertificateException;
38
39import wjhk.jupload2.exception.JUploadException;
40import wjhk.jupload2.exception.JUploadIOException;
41import wjhk.jupload2.policies.UploadPolicy;
42
43/**
44 * This class contains utilities to delegate network manipulation. It hides the
45 * management for the current upload policy connection parameters.<BR>
46 * This class goes through the following states, stored in the private
47 * connectionStatus attribute: <DIR> <LI>STATUS_NOT_INITIALIZED: default status
48 * when the instance in created. Only available action:
49 * {@link #initRequest(URL, String, boolean, boolean)} <LI>
50 * STATUS_BEFORE_SERVER_CONNECTION: the instance is initialized, and the caller
51 * may begin writing the request to this HttpConnectionHelper. All data written
52 * to it, will be stored in a {@link ByteArrayEncoderHTTP}. The connection
53 * switches to this status when the
54 * {@link #initRequest(URL, String, boolean, boolean)} is called. <LI>
55 * STATUS_WRITING_REQUEST: The network connection to the server is now opened.
56 * The content of the ByteArrayEncoderHTTP has been sent to the server. All
57 * subsequent calls to write methods will directly write on the socket to the
58 * server. The {@link #sendRequest()} method changes the connection to this
59 * status. <LI>STATUS_READING_RESPONSE: The request to the server has been
60 * totally written. No more calls to the write methods are allowed. The
61 * {@link #readHttpResponse()} is responsible to put the HttpConnectionHelper to
62 * this status. <LI>STATUS_CONNECTION_CLOSED: The response has been read. All
63 * getters can be called, to get information about the server response. The only
64 * other method allowed is the
65 * {@link #initRequest(URL, String, boolean, boolean)}, to start a new request
66 * to the server. Using the same HttpConnectionHelper allows to use the same
67 * network connection, when the allowHttpPersistent applet parameter is used.
68 * </DIR>
69 *
70 * @author etienne_sf
71 */
72public class HTTPConnectionHelper extends OutputStream {
73
74        // FIXME Does HTTPConnectionHelper really need to be an OutputStream? (Find
75        // Bug generate not closed exception)
76
77        // ////////////////////////////////////////////////////////////////////////////////////
78        // /////////////////// PRIVATE CONSTANTS
79        // ////////////////////////////////////////////////////////////////////////////////////
80        /**
81         * Indicates that the connection has not been initialized. The only
82         * authorized action in this state is a call to
83         * {@link #initRequest(URL, String, boolean, boolean)}.
84         */
85        final static private int STATUS_NOT_INITIALIZED = 0;
86
87        /**
88         * Indicates that the network connection to the server has not been opened.
89         * All data sent to the HttpConnectionHelper with write methods are sent to
90         * the current ByteArrayEncoder.
91         */
92        final static private int STATUS_BEFORE_SERVER_CONNECTION = 1;
93
94        /**
95         * Indicates that the network connection to the server is opened, but the
96         * request has not been totally sent to the server. All data sent to the
97         * HttpConnectionHelper with write methods is sent to the network
98         * connection, that is: to the current OutputStream. <BR>
99         * That is: the ByteArrayEncoder is now read only (closed).
100         */
101        final static private int STATUS_WRITING_REQUEST = 2;
102
103        /**
104         * Indicates that the network connection to the server is opened, but the
105         * request has not been totally sent to the server. All data sent to the
106         * HttpConnectionHelper with write methods is sent to the network
107         * connection, that is: to the current OutputStream. <BR>
108         * That is: the ByteArrayEncoder is now read only (closed).
109         */
110        final static private int STATUS_READING_RESPONSE = 3;
111
112        /**
113         * Indicates that the network connection to the server is now closed, that
114         * is: we've written the request, read the response, and free the server
115         * connection. If the keepAlive parameter is used, the connection may remain
116         * opened for the next request. <BR>
117         * No more action may be done on this connection helper, out of reading
118         * data, until the application do a call to
119         * {@link #initRequest(URL, String, boolean, boolean)}.
120         */
121        final static private int STATUS_CONNECTION_CLOSED = 4;
122
123        // ////////////////////////////////////////////////////////////////////////////////////
124        // /////////////////// ATTRIBUTE USED TO CONTROL THE OUTPUT TO THE SERVER
125        // ////////////////////////////////////////////////////////////////////////////////////
126        /**
127         * http boundary, for the posting multipart post.
128         */
129        private String boundary = calculateRandomBoundary();
130
131        /**
132         * Is chunk upload on for this request ?
133         */
134        private boolean bChunkEnabled;
135
136        /**
137         * Is it the last chunk ? If yes, we'll try to keep the connection open,
138         * according to the current applet configuration.
139         */
140        private boolean bLastChunk;
141
142        /**
143         * The encoder that will contain the HTTP request.
144         */
145        private ByteArrayEncoder byteArrayEncoder = null;
146
147        /**
148         * Indicates where data sent to appendXxx method should be added. It must be
149         * one of the SENDING_XXX private strings.
150         */
151        private int connectionStatus = STATUS_NOT_INITIALIZED;
152
153        /**
154         * Contains the HTTP reader. All data coming from the server response are
155         * read from it. If this attribute is null, it means that the server
156         * response has not been read.
157         */
158        private HTTPInputStreamReader httpInputStreamReader = null;
159
160        /**
161         * This stream allows the applet to get the server response. It is opened
162         * and closed as the {@link #outputStream}.
163         */
164        private PushbackInputStream inputStream = null;
165
166        /**
167         * The HTTP method: POST, GET, HEAD...
168         */
169        private String method = null;
170
171        /**
172         * This stream is open by {@link #sendRequest()}. It is closed by the
173         * {@link #readHttpResponse()} method.
174         *
175         * @see #sendRequest()
176         * @see #readHttpResponse()
177         * @see #getOutputStream()
178         */
179        private DataOutputStream outputStream = null;
180
181        /**
182         * The current proxy, if any
183         */
184        private Proxy proxy = null;
185
186        /**
187         * The network socket where the bytes should be written.
188         */
189        private Socket socket = null;
190
191        /**
192         * The current upload policy
193         */
194        private UploadPolicy uploadPolicy = null;
195
196        /**
197         * The current URL
198         */
199        private URL url = null;
200
201        /**
202         * Should we use a proxy
203         */
204        private boolean useProxy;
205
206        /**
207         * Is SSL mode on ?
208         */
209        private boolean useSSL;
210
211        // ////////////////////////////////////////////////////////////////////////////////////
212        // /////////////////////// PUBLIC METHODS
213        // ////////////////////////////////////////////////////////////////////////////////////
214
215        /**
216         * The standard constructor for this class.
217         *
218         * @param uploadPolicy
219         *            The current upload policy.
220         */
221        public HTTPConnectionHelper(UploadPolicy uploadPolicy) {
222                this.uploadPolicy = uploadPolicy;
223        }
224
225        /**
226         * The standard constructor for this class.
227         *
228         * @param url
229         *            The target URL
230         * @param method
231         *            The HTTP method (POST, GET, HEAD...)
232         * @param bChunkEnabled
233         *            Indicates if chunkUpload is enabled for this query. Put false,
234         *            if non chunked request or if it is not relevant.
235         * @param bLastChunk
236         *            Indicates whether this chunk is the last one. Put true, if non
237         *            chunked request or if it is not relevant.
238         * @param uploadPolicy
239         *            The current upload policy.
240         * @throws JUploadIOException
241         */
242        public HTTPConnectionHelper(URL url, String method, boolean bChunkEnabled,
243                        boolean bLastChunk, UploadPolicy uploadPolicy)
244                        throws JUploadIOException {
245                this.uploadPolicy = uploadPolicy;
246                initRequest(url, method, bChunkEnabled, bLastChunk);
247        }
248
249        /**
250         * The standard constructor for this class.
251         *
252         * @param url
253         *            The target URL
254         * @param bChunkEnabled
255         *            Indicates if chunkUpload is enabled for this query. Put false,
256         *            if non chunked request or if it is not relevant.
257         * @param method
258         *            The HTTP method (POST, GET, HEAD...)
259         * @param bLastChunk
260         *            Indicates whether this chunk is the last one. Put true, if non
261         *            chunked request or if it is not relevant.
262         * @throws JUploadIOException
263         */
264        public synchronized void initRequest(URL url, String method,
265                        boolean bChunkEnabled, boolean bLastChunk)
266                        throws JUploadIOException {
267                // This method expects that the connection has not been initialized yet,
268                // or that the previous request is finished.
269                if (this.connectionStatus != STATUS_NOT_INITIALIZED
270                                && this.connectionStatus != STATUS_CONNECTION_CLOSED) {
271                        throw new JUploadIOException(
272                                        "Bad status of the HttpConnectionHelper in initRequest: "
273                                                        + getStatusLabel());
274                }
275
276                // Clean any current request.
277                if (isKeepAlive()) {
278                        dispose();
279                }
280
281                // Load the new parameters.
282                this.url = url;
283                this.method = method;
284                this.bChunkEnabled = bChunkEnabled;
285                this.bLastChunk = bLastChunk;
286                // We will write to the local ByteArrayEncoder, until a connection to
287                // the server is opened.
288                initByteArrayEncoder();
289
290                // Ok, the HttpConnectionHelper is now ready to get write commands.
291                this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
292        }
293
294        /**
295         * Return the current {@link ByteArrayEncoder}. If it was not created, it is
296         * initialized.
297         *
298         * @return The current {@link ByteArrayEncoder}, null if called before the
299         *         first initialization.
300         * @throws JUploadIOException
301         * @see #initRequest(URL, String, boolean, boolean)
302         */
303        public ByteArrayEncoder getByteArrayEncoder() throws JUploadIOException {
304                return this.byteArrayEncoder;
305        }
306
307        /**
308         * @return Returns the boundary for this HTTP request.
309         */
310        public String getBoundary() {
311                return this.boundary;
312        }
313
314        /**
315         * Closes the byteArrayEncoder, create the socket (or not, depending on the
316         * current uploadPolicy, and upload history), send the request, and create
317         * the InputStream to read the server response.
318         *
319         * @throws JUploadIOException
320         */
321        public synchronized void sendRequest() throws JUploadIOException {
322                // This method expects that the connection is writing data to the
323                // server.
324                if (this.connectionStatus != STATUS_BEFORE_SERVER_CONNECTION) {
325                        throw new JUploadIOException(
326                                        "Bad status of the HttpConnectionHelper in sendRequest: "
327                                                        + getStatusLabel());
328                }
329
330                try {
331                        // We've finished with the current encoder.
332                        if (!this.byteArrayEncoder.isClosed()) {
333                                this.byteArrayEncoder.close();
334                        }
335
336                        // Let's clear any field that could have been read in a previous
337                        // step:
338                        this.httpInputStreamReader = null;
339
340                        // Only connect, if sock is null!!
341                        // ... or if we don't persist HTTP connections (patch for IIS, based
342                        // on Marc Reidy's patch)
343                        if (this.socket == null
344                                        || !this.uploadPolicy.getAllowHttpPersistent()) {
345                                this.socket = new HttpConnect(this.uploadPolicy).connect(
346                                                this.url, this.proxy);
347                                this.outputStream = new DataOutputStream(
348                                                new BufferedOutputStream(this.socket.getOutputStream()));
349                                this.inputStream = new PushbackInputStream(this.socket
350                                                .getInputStream(), 1);
351                        }
352
353                        // Send http request to server
354                        this.outputStream
355                                        .write(this.byteArrayEncoder.getEncodedByteArray());
356
357                        // The request has been sent. The current ByteArrayEncoder is now
358                        // useless. A new one is to be created for the next request.
359                        this.connectionStatus = STATUS_WRITING_REQUEST;
360
361                } catch (IOException e) {
362                        throw new JUploadIOException("Unable to open socket", e);
363                } catch (KeyManagementException e) {
364                        throw new JUploadIOException("Unable to open socket", e);
365                } catch (UnrecoverableKeyException e) {
366                        throw new JUploadIOException("Unable to open socket", e);
367                } catch (NoSuchAlgorithmException e) {
368                        throw new JUploadIOException("Unable to open socket", e);
369                } catch (KeyStoreException e) {
370                        throw new JUploadIOException("Unable to open socket", e);
371                } catch (CertificateException e) {
372                        throw new JUploadIOException("Unable to open socket", e);
373                } catch (IllegalArgumentException e) {
374                        throw new JUploadIOException("Unable to open socket", e);
375                }
376
377        }
378
379        /**
380         * Releases all reserved resources.
381         *
382         * @throws JUploadIOException
383         */
384        public synchronized void dispose() throws JUploadIOException {
385                // A try to properly clean the connection to the server.
386                try {
387                        // Let's shutdown the output, to free the server connection. Only
388                        // valid for non https connections.
389                        if (this.socket != null && !this.useSSL
390                                        && !this.socket.isOutputShutdown()) {
391                                this.socket.shutdownOutput();
392                        }
393                } catch (IOException e) {
394                        throw new JUploadIOException(e);
395                }
396                try {
397                        if (this.socket != null && !this.useSSL
398                                        && !this.socket.isInputShutdown()) {
399                                this.socket.shutdownInput();
400                        }
401                } catch (IOException e) {
402                        throw new JUploadIOException(e);
403                }
404
405                try {
406                        if (this.outputStream != null) {
407                                this.outputStream.close();
408                        }
409                } catch (IOException e) {
410                        throw new JUploadIOException(e);
411                } finally {
412                        this.outputStream = null;
413                }
414
415                try {
416                        if (this.inputStream != null) {
417                                this.inputStream.close();
418                        }
419                } catch (IOException e) {
420                        throw new JUploadIOException(e);
421                } finally {
422                        this.inputStream = null;
423                }
424
425                try {
426                        if (this.socket != null) {
427                                if (!this.socket.isClosed()) {
428                                        this.socket.close();
429                                }
430                        }
431                } catch (IOException e) {
432                        throw new JUploadIOException(e);
433                } finally {
434                        this.socket = null;
435                }
436        }
437
438        /**
439         * Return the current socket. If the byteArrayEncoder is not closed: close
440         * it, and send the request to the server.
441         *
442         * @return public Socket getSocket() { }
443         */
444
445        /**
446         * get the output stream, where HTTP data can be written.
447         *
448         * @return The current output stream to the server, where things can be
449         *         written, event after the socket is open, if the byteArrayEncoder
450         *         did not contain the full request.
451         */
452        public OutputStream getOutputStream() {
453                return this;
454        }
455
456        /**
457         * get the input stream, where HTTP server response can be read.
458         *
459         * @return The current input stream of the socket.
460         */
461        public PushbackInputStream getInputStream() {
462                return this.inputStream;
463        }
464
465        /**
466         * Get the HTTP method (HEAD, POST, GET...)
467         *
468         * @return The HTTP method
469         */
470        public String getMethod() {
471                return this.method;
472        }
473
474        /**
475         * Get the last response body.
476         *
477         * @return The full response body, that is: the HTTP body of the server
478         *         response.
479         */
480        public String getResponseBody() {
481                return this.httpInputStreamReader.getResponseBody();
482        }
483
484        /**
485         * Get the headers of the HTTP response.
486         *
487         * @return The HTTP headers.
488         */
489        public String getResponseHeaders() {
490                return this.httpInputStreamReader.getResponseHeaders();
491        }
492
493        /**
494         * Get the last response message.
495         *
496         * @return the response message, like "200 OK"
497         */
498        public String getResponseMsg() {
499                return this.httpInputStreamReader.getResponseMsg();
500        }
501
502        /**
503         * Get the label describing the current state of this connection helper.
504         *
505         * @return A text describing briefly the current connection status.
506         */
507        public synchronized String getStatusLabel() {
508                switch (this.connectionStatus) {
509                case STATUS_NOT_INITIALIZED:
510                        return "Not initialized";
511                case STATUS_BEFORE_SERVER_CONNECTION:
512                        return "Before server connection";
513                case STATUS_WRITING_REQUEST:
514                        return "Writing request to the network";
515                case STATUS_READING_RESPONSE:
516                        return "Reading server response";
517                case STATUS_CONNECTION_CLOSED:
518                        return "Connection closed";
519                }
520                return "Unknown status in HTTPConnectionHelper.getStatusLabel()";
521        }
522
523        /**
524         * Get the current socket.
525         *
526         * @return return the current Socket, opened toward the server.
527         */
528        Socket getSocket() {
529                return this.socket;
530        }
531
532        /**
533         * Append bytes to the current query. The bytes will be written to the
534         * current ByteArrayEncoder if the the connection to the server is not open,
535         * or directly to the server if the connection is opened.
536         *
537         * @param b
538         *            The byte to send to the server.
539         * @return Returns the current HttpConnectionHelper, to allow coding like
540         *         StringBuffers: a.append(b).append(c);
541         * @throws JUploadIOException
542         */
543        public synchronized HTTPConnectionHelper append(int b)
544                        throws JUploadIOException {
545                if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
546                        this.byteArrayEncoder.append(b);
547                } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
548                        try {
549                                this.outputStream.write(b);
550                        } catch (IOException e) {
551                                throw new JUploadIOException(e.getClass().getName()
552                                                + " while writing to httpDataOut", e);
553                        }
554                } else {
555                        throw new JUploadIOException(
556                                        "Wrong status in HTTPConnectionHelper.write() ["
557                                                        + getStatusLabel() + "]");
558                }
559                return this;
560        }
561
562        /**
563         * Append bytes to the current query. The bytes will be written to the
564         * current ByteArrayEncoder if the the connection to the server is not open,
565         * or directly to the server if the connection is opened.
566         *
567         * @param bytes
568         *            The bytes to send to the server.
569         * @return Returns the current HttpConnectionHelper, to allow coding like
570         *         StringBuffers: a.append(b).append(c);
571         * @throws JUploadIOException
572         */
573        public synchronized HTTPConnectionHelper append(byte[] bytes)
574                        throws JUploadIOException {
575                return this.append(bytes, 0, bytes.length);
576        }
577
578        /**
579         * Append bytes to the current query. The bytes will be written to the
580         * current ByteArrayEncoder if the the connection to the server is not open,
581         * or directly to the server if the connection is opened.
582         *
583         * @param bytes
584         *            The bytes to send to the server.
585         * @param off
586         *            The first byte to send
587         * @param len
588         *            Number of bytes to send.
589         * @return Returns the current HttpConnectionHelper, to allow coding like
590         *         StringBuffers: a.append(b).append(c);
591         * @throws JUploadIOException
592         */
593        public synchronized HTTPConnectionHelper append(byte[] bytes, int off,
594                        int len) throws JUploadIOException {
595
596                if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
597                        this.byteArrayEncoder.append(bytes);
598                } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
599                        try {
600                                this.outputStream.write(bytes, off, len);
601                        } catch (IOException e) {
602                                throw new JUploadIOException(e.getClass().getName()
603                                                + " while writing to httpDataOut", e);
604                        }
605                } else {
606                        throw new JUploadIOException(
607                                        "Wrong status in HTTPConnectionHelper.write() ["
608                                                        + getStatusLabel() + "]");
609                }
610
611                if (this.uploadPolicy.getDebugLevel() > 100) {
612                        this.uploadPolicy
613                                        .displayDebug(
614                                                        "[HTTPConnectionHelper append(byte[],int,int)] ("
615                                                                        + len
616                                                                        + " bytes appended to "
617                                                                        + (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION ? " current ByteArrayEncoder"
618                                                                                        : " socket") + ")", 101);
619                }
620
621                return this;
622        }
623
624        /**
625         * write a string to the current HTTP request.
626         *
627         * @param str
628         *            The string to write
629         * @return The current HTTPConnectionHelper
630         * @throws JUploadIOException
631         *             If any problem occurs during the writing operation.
632         * @see #append(byte[])
633         */
634        public synchronized HTTPConnectionHelper append(String str)
635                        throws JUploadIOException {
636                this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] " + str,
637                                70);
638                if (this.connectionStatus == STATUS_BEFORE_SERVER_CONNECTION) {
639                        this.byteArrayEncoder.append(str);
640                } else if (this.connectionStatus == STATUS_WRITING_REQUEST) {
641                        ByteArrayEncoder bae = new ByteArrayEncoderHTTP(this.uploadPolicy,
642                                        this.byteArrayEncoder.getBoundary(), this.byteArrayEncoder
643                                                        .getEncoding());
644                        bae.append(str);
645                        bae.close();
646                        this.append(bae);
647                }
648                return this;
649        }
650
651        /**
652         * Appends a string to the current HTTP request.
653         *
654         * @param bae
655         *            The ByteArrayEncoder to write. It is expected to be correctly
656         *            encoded. That is: it is up to the caller to check that its
657         *            encoding is the same as the current HTTP request encoding.
658         * @return The current HTTPConnectionHelper
659         * @throws JUploadIOException
660         *             If any problem occurs during the writing operation.
661         * @see #append(byte[])
662         */
663        public synchronized HTTPConnectionHelper append(ByteArrayEncoder bae)
664                        throws JUploadIOException {
665                this.uploadPolicy.displayDebug("[HTTPConnectionHelper append] "
666                                + bae.getString(), 70);
667                return this.append(bae.getEncodedByteArray());
668        }
669
670        /**
671         * Read the response of the server. This method delegates the work to the
672         * HTTPInputStreamReader. handles the chunk HTTP response.
673         *
674         * @return The HTTP status. Should be 200, when everything is right.
675         * @throws JUploadException
676         */
677        public synchronized int readHttpResponse() throws JUploadException {
678                // This method expects that the connection is writing data to the
679                // server.
680                if (this.connectionStatus != STATUS_WRITING_REQUEST) {
681                        throw new JUploadIOException(
682                                        "Bad status of the HttpConnectionHelper in readHttpResponse: "
683                                                        + getStatusLabel());
684                }
685                this.connectionStatus = STATUS_READING_RESPONSE;
686
687                // Let's connect in InputStream to read this server response.
688                if (this.httpInputStreamReader == null) {
689                        this.httpInputStreamReader = new HTTPInputStreamReader(this,
690                                        this.uploadPolicy);
691                }
692
693                // Let's do the job
694                try {
695                        this.outputStream.flush();
696                } catch (IOException ioe) {
697                        throw new JUploadIOException("flushing outputStream, in "
698                                        + getClass().getName() + ".readHttpResponse()");
699                }
700                this.httpInputStreamReader.readHttpResponse();
701
702                if (this.httpInputStreamReader.gotClose) {
703                        // RFC 2868, section 8.1.2.1
704                        dispose();
705                }
706
707                // We got the response
708                this.connectionStatus = STATUS_CONNECTION_CLOSED;
709
710                // FIXME Should close the connection, from time to time...
711
712                //
713                return this.httpInputStreamReader.gethttpStatusCode();
714        }
715
716        // ////////////////////////////////////////////////////////////////////////////////////
717        // /////////////////////// PRIVATE METHODS
718        // ////////////////////////////////////////////////////////////////////////////////////
719
720        /**
721         * creating of a new {@link ByteArrayEncoderHTTP}, and initializing of the
722         * following header items: First line (POST currentProtocol URI), Host,
723         * Connection, Keep-Alive, Proxy-Connection.
724         *
725         * @throws JUploadIOException
726         */
727        private synchronized void initByteArrayEncoder() throws JUploadIOException {
728                if (this.byteArrayEncoder != null && !this.byteArrayEncoder.isClosed()) {
729                        this.byteArrayEncoder.close();
730                        this.byteArrayEncoder = null;
731                }
732                this.byteArrayEncoder = new ByteArrayEncoderHTTP(this.uploadPolicy,
733                                this.boundary);
734                this.connectionStatus = STATUS_BEFORE_SERVER_CONNECTION;
735                this.proxy = null;
736                try {
737                        this.proxy = ProxySelector.getDefault().select(this.url.toURI())
738                                        .get(0);
739                } catch (URISyntaxException e) {
740                        throw new JUploadIOException("Error while managing url "
741                                        + this.url.toExternalForm(), e);
742                }
743                this.useProxy = ((this.proxy != null) && (this.proxy.type() != Proxy.Type.DIRECT));
744                this.useSSL = this.url.getProtocol().equals("https");
745
746                // Header: Request line
747                // Let's clear it. Useful only for chunked uploads.
748                this.byteArrayEncoder.append(this.method);
749                this.byteArrayEncoder.append(" ");
750                this.uploadPolicy.displayDebug("[initByteArrayEncoder] proxy=" + proxy
751                                + ", proxy.type=" + this.proxy.type() + ", useProxy="
752                                + useProxy + ", url.host=" + this.url.getHost() + ", url.port="
753                                + this.url.getPort(), 80);
754                if (this.useProxy && (!this.useSSL)) {
755                        // with a proxy we need the absolute URL, but only if not
756                        // using SSL. (with SSL, we first use the proxy CONNECT method,
757                        // and then a plain request.)
758                        this.byteArrayEncoder.append(this.url.getProtocol()).append("://")
759                                        .append(this.url.getHost());
760                        // TODO port in proxy mode: to be tested !
761                        if (this.url.getPort() > 0) {
762                                this.byteArrayEncoder.append(":").append(
763                                                Integer.toString(this.url.getPort()));
764                        }
765                }
766                this.byteArrayEncoder.append(this.url.getPath());
767
768                // Append the query params.
769                // TODO: This probably can be removed as we now have everything in POST
770                // data. However in order to be
771                // backwards-compatible, it stays here for now. So we now provide
772                // *both* GET and POST params.
773                if (null != this.url.getQuery() && !"".equals(this.url.getQuery()))
774                        this.byteArrayEncoder.append("?").append(this.url.getQuery());
775
776                this.byteArrayEncoder.append(" ").append(
777                                this.uploadPolicy.getServerProtocol()).append("\r\n");
778
779                // Header: General
780                this.byteArrayEncoder.append("Host: ").append(this.url.getHost())
781                                .append("\r\nAccept: */*\r\n");
782                // We do not want gzipped or compressed responses, so we must
783                // specify that here (RFC 2616, Section 14.3)
784                this.byteArrayEncoder.append("Accept-Encoding: identity\r\n");
785
786                // Seems like the Keep-alive doesn't work properly, at least on my
787                // local dev (Etienne).
788                if (!this.uploadPolicy.getAllowHttpPersistent()) {
789                        this.byteArrayEncoder.append("Connection: close\r\n");
790                } else {
791                        if (!this.bChunkEnabled
792                                        || this.bLastChunk
793                                        || this.useProxy
794                                        || !this.uploadPolicy.getServerProtocol()
795                                                        .equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
796                                this.byteArrayEncoder.append("Connection: close\r\n");
797                        } else {
798                                this.byteArrayEncoder.append("Keep-Alive: 300\r\n");
799                                if (this.useProxy)
800                                        this.byteArrayEncoder
801                                                        .append("Proxy-Connection: keep-alive\r\n");
802                                else
803                                        this.byteArrayEncoder.append("Connection: keep-alive\r\n");
804                        }
805                }
806
807                // Get specific headers for this upload.
808                this.uploadPolicy.onAppendHeader(this.byteArrayEncoder);
809        }
810
811        /**
812         * Indicates whether the current socket should be reused ... if any.
813         */
814        private boolean isKeepAlive() {
815                if (this.socket == null) {
816                        return false;
817                } else if (!this.uploadPolicy.getAllowHttpPersistent()) {
818                        return false;
819                } else {
820                        if (!this.bChunkEnabled
821                                        || this.bLastChunk
822                                        || this.useProxy
823                                        || !this.uploadPolicy.getServerProtocol()
824                                                        .equals("HTTP/1.1")) { // RFC 2086, section 19.7.1
825                                return false;
826                        } else {
827                                return true;
828                        }
829                }
830        }
831
832        /**
833         * Construction of a random boundary, to separate the uploaded files, in the
834         * HTTP upload request.
835         *
836         * @return The calculated boundary.
837         */
838        private final String calculateRandomBoundary() {
839                StringBuffer sbRan = new StringBuffer(11);
840                sbRan.append("-----------------------------");
841                String alphaNum = "1234567890abcdefghijklmnopqrstuvwxyz";
842                int num;
843                for (int i = 0; i < 11; i++) {
844                        num = (int) (Math.random() * (alphaNum.length() - 1));
845                        sbRan.append(alphaNum.charAt(num));
846                }
847                return sbRan.toString();
848        }
849
850        // ////////////////////////////////////////////////////////////////////////////////////
851        // /////////////////// OVERRIDE OF OutputStream METHODS
852        // ////////////////////////////////////////////////////////////////////////////////////
853        /** {@inheritDoc} */
854        @Override
855        public void write(int b) throws IOException {
856                try {
857                        append(b);
858                } catch (JUploadIOException e) {
859                        // Hum, HTTPConnectionHelper catch IOException, and throws a
860                        // JUploadIOException. Now we get the cause, that is the original
861                        // IOException. Not optimized.
862                        if (e.getCause() == null) {
863                                // This should not happen
864                                throw new IOException();
865                        } else if (e.getCause() instanceof IOException) {
866                                throw (IOException) e.getCause();
867                        } else {
868                                // Hum, can something like an OutOfMemory. We must throw it.
869                                throw new IOException(e.getCause().getClass().getName() + ": "
870                                                + e.getCause().getMessage());
871                        }
872                }
873        }
874
875        /** {@inheritDoc} */
876        @Override
877        public void write(byte[] b, int off, int len) throws IOException {
878                try {
879                        append(b, off, len);
880                } catch (JUploadIOException e) {
881                        // Hum, HTTPConnectionHelper catch IOException, and throws a
882                        // JUploadIOException. Now we get the cause, that is the original
883                        // IOException. Not optimized.
884                        if (e.getCause() == null) {
885                                // This should not happen
886                                throw new IOException();
887                        } else if (e.getCause() instanceof IOException) {
888                                throw (IOException) e.getCause();
889                        } else {
890                                // Hum, can something like an OutOfMemory. We must throw it.
891                                throw new IOException(e.getCause().getClass().getName() + ": "
892                                                + e.getCause().getMessage());
893                        }
894                }
895        }
896
897        /** {@inheritDoc} */
898        @Override
899        public void write(byte[] b) throws IOException {
900                write(b, 0, b.length);
901        }
902
903        /**
904         * This method is the override of {@link OutputStream#close()} one. It may
905         * not been called. You must use the {@link #sendRequest()} or
906         * {@link #readHttpResponse()} methods instead.
907         *
908         * @see java.io.OutputStream#close()
909         */
910        @Override
911        public void close() throws IOException {
912                throw new IOException(
913                                "Forbidden action (HTTPConnectionHelper.close()). Please use the "
914                                                + getClass().getName() + ".sendRequest() method");
915        }
916
917        /**
918         * Flushes the output stream. Useful only when the HTTPConnectionHelper is
919         * writing to the socket toward the server, that is when the status is:
920         * STATUS_WRITING_REQUEST.
921         *
922         * @see java.io.OutputStream#flush()
923         */
924        @Override
925        public void flush() throws IOException {
926                if (this.connectionStatus == STATUS_WRITING_REQUEST) {
927                        this.outputStream.flush();
928                } else {
929                        throw new IOException("Wrong status in " + getClass().getName()
930                                        + ".flush method: " + getStatusLabel());
931                }
932        }
933}
Note: See TracBrowser for help on using the repository browser.