View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.fcgi.generator;
20  
21  import java.nio.ByteBuffer;
22  import java.nio.charset.Charset;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.eclipse.jetty.fcgi.FCGI;
27  import org.eclipse.jetty.http.HttpField;
28  import org.eclipse.jetty.http.HttpFields;
29  import org.eclipse.jetty.io.ByteBufferPool;
30  import org.eclipse.jetty.util.BufferUtil;
31  import org.eclipse.jetty.util.Callback;
32  
33  public class ClientGenerator extends Generator
34  {
35      // To keep the algorithm simple, and given that the max length of a
36      // frame is 0xFF_FF we allow the max length of a name (or value) to be
37      // 0x7F_FF - 4 (the 4 is to make room for the name (or value) length).
38      public static final int MAX_PARAM_LENGTH = 0x7F_FF - 4;
39  
40      public ClientGenerator(ByteBufferPool byteBufferPool)
41      {
42          super(byteBufferPool);
43      }
44  
45      public Result generateRequestHeaders(int request, HttpFields fields, Callback callback)
46      {
47          request &= 0xFF_FF;
48  
49          Charset utf8 = Charset.forName("UTF-8");
50          List<byte[]> bytes = new ArrayList<>(fields.size() * 2);
51          int fieldsLength = 0;
52          for (HttpField field : fields)
53          {
54              String name = field.getName();
55              byte[] nameBytes = name.getBytes(utf8);
56              if (nameBytes.length > MAX_PARAM_LENGTH)
57                  throw new IllegalArgumentException("Field name " + name + " exceeds max length " + MAX_PARAM_LENGTH);
58              bytes.add(nameBytes);
59  
60              String value = field.getValue();
61              byte[] valueBytes = value.getBytes(utf8);
62              if (valueBytes.length > MAX_PARAM_LENGTH)
63                  throw new IllegalArgumentException("Field value " + value + " exceeds max length " + MAX_PARAM_LENGTH);
64              bytes.add(valueBytes);
65  
66              int nameLength = nameBytes.length;
67              fieldsLength += bytesForLength(nameLength);
68  
69              int valueLength = valueBytes.length;
70              fieldsLength += bytesForLength(valueLength);
71  
72              fieldsLength += nameLength;
73              fieldsLength += valueLength;
74          }
75  
76          // Worst case FCGI_PARAMS frame: long name + long value - both of MAX_PARAM_LENGTH
77          int maxCapacity = 4 + 4 + 2 * MAX_PARAM_LENGTH;
78  
79          // One FCGI_BEGIN_REQUEST + N FCGI_PARAMS + one last FCGI_PARAMS
80  
81          ByteBuffer beginRequestBuffer = byteBufferPool.acquire(16, false);
82          BufferUtil.clearToFill(beginRequestBuffer);
83          Result result = new Result(byteBufferPool, callback);
84          result = result.append(beginRequestBuffer, true);
85  
86          // Generate the FCGI_BEGIN_REQUEST frame
87          beginRequestBuffer.putInt(0x01_01_00_00 + request);
88          beginRequestBuffer.putInt(0x00_08_00_00);
89          // Hardcode RESPONDER role and KEEP_ALIVE flag
90          beginRequestBuffer.putLong(0x00_01_01_00_00_00_00_00L);
91          beginRequestBuffer.flip();
92  
93          int index = 0;
94          while (fieldsLength > 0)
95          {
96              int capacity = 8 + Math.min(maxCapacity, fieldsLength);
97              ByteBuffer buffer = byteBufferPool.acquire(capacity, true);
98              BufferUtil.clearToFill(buffer);
99              result = result.append(buffer, true);
100 
101             // Generate the FCGI_PARAMS frame
102             buffer.putInt(0x01_04_00_00 + request);
103             buffer.putShort((short)0);
104             buffer.putShort((short)0);
105             capacity -= 8;
106 
107             int length = 0;
108             while (index < bytes.size())
109             {
110                 byte[] nameBytes = bytes.get(index);
111                 int nameLength = nameBytes.length;
112                 byte[] valueBytes = bytes.get(index + 1);
113                 int valueLength = valueBytes.length;
114 
115                 int required = bytesForLength(nameLength) + bytesForLength(valueLength) + nameLength + valueLength;
116                 if (required > capacity)
117                     break;
118 
119                 putParamLength(buffer, nameLength);
120                 putParamLength(buffer, valueLength);
121                 buffer.put(nameBytes);
122                 buffer.put(valueBytes);
123 
124                 length += required;
125                 fieldsLength -= required;
126                 capacity -= required;
127                 index += 2;
128             }
129 
130             buffer.putShort(4, (short)length);
131             buffer.flip();
132         }
133 
134 
135         ByteBuffer lastParamsBuffer = byteBufferPool.acquire(8, false);
136         BufferUtil.clearToFill(lastParamsBuffer);
137         result = result.append(lastParamsBuffer, true);
138 
139         // Generate the last FCGI_PARAMS frame
140         lastParamsBuffer.putInt(0x01_04_00_00 + request);
141         lastParamsBuffer.putInt(0x00_00_00_00);
142         lastParamsBuffer.flip();
143 
144         return result;
145     }
146 
147     private int putParamLength(ByteBuffer buffer, int length)
148     {
149         int result = bytesForLength(length);
150         if (result == 4)
151             buffer.putInt(length | 0x80_00_00_00);
152         else
153             buffer.put((byte)length);
154         return result;
155     }
156 
157     private int bytesForLength(int length)
158     {
159         return length > 127 ? 4 : 1;
160     }
161 
162     public Result generateRequestContent(int request, ByteBuffer content, boolean lastContent, Callback callback)
163     {
164         return generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDIN);
165     }
166 }