initial commit
[fcgi] / java / FCGIInputStream.java
1 /*
2  * @(#)FCGIInputStream.java
3  *
4  *      FastCGi compatibility package Interface
5  *
6  *
7  *  Copyright (c) 1996 Open Market, Inc.
8  *
9  * See the file "LICENSE.TERMS" for information on usage and redistribution
10  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11  *
12  * $Id: FCGIInputStream.java,v 1.4 2000/03/21 12:12:25 robs Exp $
13  */
14 package com.fastcgi;
15
16 import java.io.*;
17
18 /**
19  * This stream manages buffered reads of FCGI messages.
20  */
21 public class FCGIInputStream extends InputStream
22 {
23     private static final String RCSID = "$Id: FCGIInputStream.java,v 1.4 2000/03/21 12:12:25 robs Exp $";
24
25     /* Stream vars */
26
27     public int rdNext;
28     public int stop;
29     public boolean isClosed;
30
31     /* require methods to set, get and clear */
32     private int errno;
33     private Exception errex;
34
35     /* data vars */
36
37     public byte buff[];
38     public int buffLen;
39     public int buffStop;
40     public int type;
41     public int contentLen;
42     public int paddingLen;
43     public boolean skip;
44     public boolean eorStop;
45     public FCGIRequest request;
46
47     public InputStream in;
48
49
50     /**
51     * Creates a new input stream to manage fcgi prototcol stuff
52     * @param in the input stream  bufLen  length of buffer streamType
53     */
54     public FCGIInputStream(FileInputStream inStream, int bufLen,
55         int streamType,
56         FCGIRequest inReq) {
57
58         in = inStream;
59         buffLen = Math.min(bufLen,FCGIGlobalDefs.def_FCGIMaxLen);
60         buff = new byte[buffLen];
61         type = streamType;
62         stop = rdNext = buffStop = 0;
63         isClosed = false;
64         contentLen = 0;
65         paddingLen = 0;
66         skip = false;
67         eorStop = false;
68         request = inReq;
69
70     }
71     /**
72     * Reads a byte of data. This method will block if no input is
73     * available.
74     * @return  the byte read, or -1 if the end of the
75     *      stream is reached.
76     * @exception IOException If an I/O error has occurred.
77     */
78     public int read() throws IOException {
79         if (rdNext != stop) {
80             return buff[rdNext++] & 0xff;
81         }
82         if (isClosed){
83             return -1;
84         }
85         fill();
86         if (rdNext != stop){
87             return buff[rdNext++] & 0xff;
88         }
89         return -1;
90     }
91     /**
92     * Reads into an array of bytes.  This method will
93     * block until some input is available.
94     * @param b the buffer into which the data is read
95     * @return  the actual number of bytes read, -1 is
96     *      returned when the end of the stream is reached.
97     * @exception IOException If an I/O error has occurred.
98     */
99     public int read(byte b[]) throws IOException {
100         return read(b, 0, b.length);
101     }
102
103     /**
104     * Reads into an array of bytes.
105     * Blocks until some input is available.
106     * @param b the buffer into which the data is read
107     * @param off the start offset of the data
108     * @param len the maximum number of bytes read
109     * @return  the actual number of bytes read, -1 is
110     *      returned when the end of the stream is reached.
111     * @exception IOException If an I/O error has occurred.
112     */
113     public int read(byte b[], int off, int len) throws IOException {
114         int m, bytesMoved;
115
116         if (len <= 0){
117             return 0;
118         }
119         /*
120         *Fast path: len bytes already available.
121         */
122
123         if (len <= stop - rdNext){
124             System.arraycopy(buff, rdNext, b, off, len);
125             rdNext += len;
126             return len;
127         }
128         /*
129         *General case: stream is closed or fill needs to be called
130         */
131         bytesMoved = 0;
132         for(;;){
133             if (rdNext != stop){
134                 m = Math.min(len - bytesMoved, stop - rdNext);
135                 System.arraycopy(buff, rdNext, b, off, m);
136                 bytesMoved += m;
137                 rdNext += m;
138                 if (bytesMoved == len)
139                     return bytesMoved;
140                 off += m;
141             }
142             if (isClosed){
143                 return bytesMoved;
144             }
145             fill();
146
147         }
148     }
149     /**
150     * Reads into an array of bytes.  This method will
151     * block until some input is available.
152     * @param b the buffer into which the data is read
153     * @param off the start offset of the data
154     * @param len the maximum number of bytes read
155     * @return  the actual number of bytes read, -1 is
156     *      returned when the end of the stream is reached.
157     * @exception IOException If an I/O error has occurred.
158     */
159     public void  fill() throws IOException {
160         byte[] headerBuf = new byte[FCGIGlobalDefs.def_FCGIHeaderLen];
161         int headerLen = 0;
162         int status = 0;
163         int count = 0;
164         for(;;) {
165             /*
166             * If buffer is empty, do a read
167             */
168             if (rdNext == buffStop) {
169                 try {
170                     count = in.read(buff, 0, buffLen);
171                 } catch (IOException e) {
172                     setException(e);
173                     return;
174                 }
175                 if (count == 0) {
176                     setFCGIError(FCGIGlobalDefs.def_FCGIProtocolError);
177                     return;
178                 }
179                 rdNext = 0;
180                 buffStop = count;       // 1 more than we read
181             }
182             /* Now buf is not empty: If the current record contains more content
183              * bytes, deliver all that are present in buff to callers buffer
184              * unless he asked for less than we have, in which case give him less
185              */
186             if (contentLen > 0) {
187                 count = Math.min(contentLen, buffStop - rdNext);
188                 contentLen -= count;
189                 if (!skip) {
190                     stop = rdNext + count;
191                     return;
192                 }
193                 else {
194                     rdNext += count;
195                     if (contentLen > 0) {
196                         continue;
197                     }
198                     else {
199                         skip = false;
200                     }
201                 }
202             }
203             /* Content has been consumed by client.
204              * If record was padded, skip over padding
205              */
206             if (paddingLen > 0) {
207                 count = Math.min(paddingLen, buffStop - rdNext);
208                 paddingLen -= count;
209                 rdNext += count;
210                 if (paddingLen > 0) {
211                     continue;    // more padding to read
212                 }
213             }
214             /* All done with current record, including the padding.
215              * If we are in a recursive call from Process Header, deliver EOF
216              */
217             if (eorStop){
218                 stop = rdNext;
219                 isClosed = true;
220                 return;
221             }
222             /*
223              * Fill header with bytes from input buffer - get the whole header.
224              */
225             count = Math.min(headerBuf.length - headerLen, buffStop - rdNext);
226             System.arraycopy(buff,rdNext, headerBuf, headerLen, count);
227             headerLen += count;
228             rdNext  += count;
229             if (headerLen < headerBuf.length) {
230                 continue;
231             }
232             headerLen = 0;
233             /*
234              * Interperet the header. eorStop prevents ProcessHeader from
235              * reading past the end of record when using stream to read content
236              */
237             eorStop = true;
238             stop = rdNext;
239             status = 0;
240             status = new FCGIMessage(this).processHeader(headerBuf);
241             eorStop = false;
242             isClosed = false;
243             switch (status){
244             case FCGIGlobalDefs.def_FCGIStreamRecord:
245                 if (contentLen == 0) {
246                     stop = rdNext;
247                     isClosed = true;
248                     return;
249                 }
250                 break;
251             case FCGIGlobalDefs.def_FCGISkip:
252                 skip = true;
253                 break;
254             case FCGIGlobalDefs.def_FCGIBeginRecord:
255                 /*
256                 * If this header marked the beginning of a new
257                 * request, return role info to caller
258                 */
259                 return;
260             case FCGIGlobalDefs.def_FCGIMgmtRecord:
261                 break;
262             default:
263                 /*
264                 * ASSERT
265                 */
266                 setFCGIError(status);
267                 return;
268
269             }
270         }
271     }
272
273     /**
274      * Skips n bytes of input.
275      * @param n the number of bytes to be skipped
276      * @return  the actual number of bytes skipped.
277      * @exception IOException If an I/O error has occurred.
278      */
279     public long skip(long n) throws IOException {
280         byte data[] = new byte[(int)n];
281         return in.read(data);
282     }
283
284     /*
285      * An FCGI error has occurred. Save the error code in the stream
286      * for diagnostic purposes and set the stream state so that
287      * reads return EOF
288      */
289     public void setFCGIError(int errnum) {
290         /*
291         * Preserve only the first error.
292         */
293         if(errno == 0) {
294             errno = errnum;
295         }
296         isClosed = true;
297     }
298     /*
299     * An Exception has occurred. Save the Exception in the stream
300     * for diagnostic purposes and set the stream state so that
301     * reads return EOF
302     */
303     public void setException(Exception errexpt) {
304         /*
305         * Preserve only the first error.
306         */
307         if(errex == null) {
308             errex = errexpt;
309         }
310         isClosed = true;
311     }
312
313     /*
314     * Clear the stream error code and end-of-file indication.
315     */
316     public void clearFCGIError() {
317         errno = 0;
318         /*
319         * isClosed = false;
320         * XXX: should clear isClosed but work is needed to make it safe
321         * to do so.
322         */
323     }
324     /*
325     * Clear the stream error code and end-of-file indication.
326     */
327     public void clearException() {
328         errex = null;
329         /*
330         * isClosed = false;
331         * XXX: should clear isClosed but work is needed to make it safe
332         * to do so.
333         */
334     }
335
336     /*
337     * accessor method since var is private
338     */
339     public int getFCGIError() {
340         return errno;
341     }
342     /*
343     * accessor method since var is private
344     */
345     public Exception getException() {
346         return errex;
347     }
348     /*
349     * Re-initializes the stream to read data of the specified type.
350     */
351     public void setReaderType(int streamType) {
352
353         type = streamType;
354         eorStop = false;
355         skip = false;
356         contentLen = 0;
357         paddingLen = 0;
358         stop = rdNext;
359         isClosed = false;
360     }
361
362     /*
363     * Close the stream. This method does not really exist for BufferedInputStream in java,
364     * but is implemented here for compatibility with the FCGI structures being used. It
365     * doent really throw any IOExceptions either, but that's there for compatiblity with
366     * the InputStreamInterface.
367     */
368     public void close() throws IOException{
369         isClosed = true;
370         stop = rdNext;
371     }
372
373     /*
374     * Returns the number of bytes that can be read without blocking.
375     */
376
377     public int available() throws IOException {
378         return stop - rdNext + in.available();
379     }
380
381 }