View Javadoc

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