2 * This document is a part of the source code and related artifacts
\r
3 * for CollectionSpace, an open source collections management system
\r
4 * for museums and related institutions:
\r
6 * http://www.collectionspace.org
\r
7 * http://wiki.collectionspace.org
\r
9 * Copyright (c) 2009 Regents of the University of California
\r
11 * Licensed under the Educational Community License (ECL), Version 2.0.
\r
12 * You may not use this file except in compliance with this License.
\r
14 * You may obtain a copy of the ECL 2.0 License at
\r
15 * https://source.collectionspace.org/collection-space/LICENSE.txt
\r
17 * Unless required by applicable law or agreed to in writing, software
\r
18 * distributed under the License is distributed on an "AS IS" BASIS,
\r
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
20 * See the License for the specific language governing permissions and
\r
21 * limitations under the License.
\r
24 package org.collectionspace.services.IntegrationTests.xmlreplay;
\r
26 import org.collectionspace.services.common.api.Tools;
\r
29 import java.util.ArrayList;
\r
30 import java.util.List;
\r
32 public class PayloadLogger{
\r
34 public static void saveReport(){
\r
35 reporter.writeTable();
\r
38 private static Reporter reporter = new Reporter();
\r
40 private static volatile int ID = 1000;
\r
41 public String getID(){
\r
45 private static String DD = "--";
\r
46 private static String LABEL="LABEL: ";
\r
48 static class Reporter {
\r
50 table.append("<html><body><table border='1'>\r\n");
\r
52 public static final String START = "<tr><td>";
\r
53 public static final String SEP = "</td><td>";
\r
54 public static final String END = "</td></tr>";
\r
56 public void writeTable(){
\r
57 table.append("</table></body></html>");
\r
58 saveFile("./xml/", "results.html", table.toString());
\r
61 private StringBuffer table = new StringBuffer();
\r
63 public synchronized void finished(HttpTraffic traffic){
\r
64 String direction = traffic.isRequest ?
\r
65 "<span style='background-color: lightgreen;'>==></span>"
\r
71 .append(traffic==null ? "null" : traffic.toRow(SEP, this))
\r
75 public synchronized void finished(List<HttpTraffic> trafficList){
\r
76 for (HttpTraffic traffic: trafficList){
\r
81 public static String link(String desc, String url){
\r
82 return "<a href='"+url+"'>"+desc+"</a>";
\r
84 public static final String newline = "<br />\r\n";
\r
89 static class HttpTraffic {
\r
90 public HttpTraffic(){
\r
91 payloads = new ArrayList<Part>();
\r
93 public List<Part> payloads;
\r
94 public String method = "";
\r
95 public String url = "";
\r
96 public String queryParams = "";
\r
97 public String message = "";
\r
98 public int responseCode = 0;
\r
99 public String boundary = "";
\r
100 public String location = "";
\r
101 public long contentLength = -1;
\r
102 public boolean isRequest = true;
\r
103 public boolean extra = false;
\r
104 public String ID = "0";
\r
105 public int nexti = 0;
\r
107 public Part getPart(String label){
\r
108 for (Part part : payloads){
\r
109 if (part.label.equalsIgnoreCase(label)){
\r
116 public String toRow(String sep, Reporter reporter){
\r
117 StringBuffer b = new StringBuffer();
\r
118 for (Part part : payloads){
\r
119 String name = part.label;
\r
120 if (part.filename.length()==0){
\r
123 if (name.trim().length()<=0){
\r
126 name = name+" ("+part.filetype+')';
\r
127 String link = reporter.link(name, part.filename);
\r
129 .append(reporter.newline);
\r
131 String parts = b.toString();
\r
151 static class Part {
\r
152 public Part(String boundary){
\r
153 this.boundary = boundary;
\r
155 public boolean isMultipart(){
\r
156 return boundary.length()>0;
\r
158 public String toString(){
\r
159 return "Part:"+label+";";
\r
161 public String filename = "";
\r
162 public String filetype = "";
\r
163 public String boundary;
\r
164 public StringBuffer buffer = new StringBuffer();
\r
165 public String getContent(){
\r
166 return buffer.toString();
\r
168 public String label = "";
\r
169 public int readPart(String[]lines, int i){
\r
171 boolean readingPartHeaders = true;
\r
172 while(readingPartHeaders){
\r
173 line = killTrailingWS(lines[i]);
\r
174 if (line.toUpperCase().startsWith(LABEL)){
\r
175 this.label = line.substring(LABEL.length()).trim();
\r
176 } else if (line.trim().length()==0){
\r
177 readingPartHeaders = false;
\r
181 while (i<lines.length){
\r
183 if (line.startsWith(DD+boundary)){
\r
186 this.buffer.append(line).append("\r\n"); //todo: maybe don't add CRLF on last line.
\r
191 public int readRemaining(String [] lines, int i, long contentLength){
\r
194 while (i<lines.length && bytesRead<contentLength){
\r
195 line = killTrailingWS(lines[i]);
\r
196 if (line.startsWith("HTTP/1.1")){
\r
199 int read = line.length();
\r
201 buffer.append(line).append("\r\n"); //todo: maybe don't add CRLF on last line.
\r
208 public static String parseBoundary(String headerLine) {
\r
209 if (Tools.isEmpty(headerLine)) {
\r
212 String lineUP = headerLine.toUpperCase();
\r
213 String boundary = "";
\r
214 if (lineUP.startsWith("CONTENT-TYPE:")) {
\r
215 String[] boundaryTokens = headerLine.split("boundary=");
\r
216 if (boundaryTokens.length == 2) {
\r
217 boundary = killTrailingWS(boundaryTokens[1]);
\r
219 // Content-Type: multipart/mixed; boundary=a97c20ab-3ef6-4adc-82b0-6cf28c450faf;charset=ISO-8859-1
\r
221 String[] boundaryTerm = boundary.split(";");
\r
222 boundary = boundaryTerm[0];
\r
224 } else if (boundaryTokens.length > 2) {
\r
225 System.err.println("WARNING: too many tokens after boundary= on Content-Type: header line: " + headerLine);
\r
231 /** places the boundary on the HttpTraffic in parameter object if boundary found in header "Content-Type:".
\r
232 * @return the index of the NEXT line the caller should read. */
\r
233 protected static int readHeaders(HttpTraffic traffic, String[]lines, int i){
\r
234 int lineCount = lines.length;
\r
235 String line, lineUP;
\r
236 // Now read headers until we are ready for payload or parts.
\r
237 while (i<lineCount){
\r
239 if (line.trim().length()==0){ //blank line seen: end of headers.
\r
242 } else { //still reading outer headers.
\r
243 lineUP = line.toUpperCase().trim();
\r
244 if (lineUP.startsWith("CONTENT-TYPE:")){
\r
245 String[] boundaryTokens = line.split("boundary=");
\r
246 if (boundaryTokens.length == 2){
\r
247 traffic.boundary = killTrailingWS(boundaryTokens[1]);
\r
249 } else if (boundaryTokens.length > 2){
\r
250 System.err.println("WARNING: too many tokens after boundary= on Content-Type: header line: "+line);
\r
252 } else if (lineUP.startsWith("LOCATION: ")){
\r
253 traffic.location = killTrailingWS(line.substring("LOCATION: ".length()));
\r
254 } else if (lineUP.startsWith("CONTENT-LENGTH: ")){
\r
255 traffic.contentLength = Integer.parseInt(killTrailingWS(line.substring("CONTENT-LENGTH: ".length())));
\r
267 private static String killTrailingWS(String s){
\r
268 int i = s.length();
\r
270 char c = s.charAt(i-1);
\r
271 if (c=='\r' || c=='\n' || c==' '){
\r
278 return s.substring(0, i);
\r
281 public static HttpTraffic readPayloads(String fullPayload, String boundary, long contentLength){
\r
282 HttpTraffic traffic = new HttpTraffic();
\r
283 traffic.contentLength = contentLength;
\r
284 traffic.boundary = boundary;
\r
285 String [] lines = fullPayload.split("\\n", -1);
\r
286 readPayloads(traffic, lines, 0);
\r
290 protected static int readPayloads(HttpTraffic traffic, String[]lines, int i){
\r
291 if (traffic.boundary.length()<=0){ //END of headers, and no boundary, so read remaining and return.
\r
292 if (traffic.contentLength == 0){
\r
295 Part part = new Part("");
\r
296 traffic.payloads.add(part);
\r
297 i = part.readRemaining(lines, i, traffic.contentLength);
\r
300 int lineCount = lines.length;
\r
302 while (i<lineCount){
\r
303 //rest of message is payloads.
\r
306 if (line.startsWith( DD + traffic.boundary + DD )){ //this is the ending boundary.
\r
307 //close and accept payload chunk.
\r
308 i++; //bump past last boundary. There might be more traffic after this.
\r
310 } else if (line.startsWith(DD + traffic.boundary)){ //this is a first or middle boundary, but not last boundary.
\r
311 i++; //bump past boundary
\r
312 //begin payload chunk
\r
313 Part part = new Part(traffic.boundary);
\r
314 traffic.payloads.add(part);
\r
315 i = part.readPart(lines, i);
\r
318 //if (line.trim().length()>0){
\r
319 // System.err.println("********** Skipping line: "+line); //either parser error, or something is outside of a boundary.
\r
328 private HttpTraffic parseForward(String forward, int nexti){
\r
329 HttpTraffic forwardTraffic = new HttpTraffic();
\r
330 forwardTraffic.isRequest = true;
\r
331 forwardTraffic.ID = getID();
\r
332 //String[] lines = forward.split("\\r\\n", -1);
\r
333 String[] lines = forward.split("\\n", -1);
\r
334 int lineCount = lines.length;
\r
338 // Read the first line, and figure out if GET, POST, etc., and the URI
\r
340 while (line.trim().length()==0){
\r
342 if (i>=lineCount-1){
\r
347 String[] tokens = line.split(" ", -1);
\r
348 forwardTraffic.method = tokens[0];
\r
349 String urlString = tokens[1];
\r
350 String[] urlHalves = urlString.split("\\?", -1); //look for a query string of the form /foo/bar?param=baz and break on question mark.
\r
351 forwardTraffic.url = urlHalves[0];
\r
352 if (urlHalves.length > 1){
\r
353 forwardTraffic.queryParams = urlHalves[1];
\r
357 //if (forwardTraffic.method.equals("GET")|| forwardTraffic.method.equals("DELETE")){
\r
358 // return forwardTraffic;
\r
360 // Now read headers until we are ready for payload or parts.
\r
361 i = readHeaders(forwardTraffic, lines, i);
\r
364 if ( (i<lines.length-1) && (forwardTraffic.contentLength<=0) ) { //0 means a 0 was seen, -1 means no header was seen, as will be the case in GET or DELETE.
\r
366 //there are more lines, but content-length header was zero,
\r
367 // this means we are getting keep-alive bunches of DELETEs or OKs back.
\r
368 System.err.println("###### extra requests in this one."+getID());
\r
369 String filename = getID()+'_'+forwardTraffic.method+'_'+forwardTraffic.url.replaceAll("/", "_")+".requests";
\r
370 saveFile("./xml", filename, forward);
\r
371 return forwardTraffic;
\r
375 // We are past headers now. The rest of message is payloads.
\r
376 i = readPayloads(forwardTraffic, lines, i); //messes with forwardTraffic and places parts in it.
\r
377 forwardTraffic.nexti = i;
\r
378 return forwardTraffic;
\r
381 private HttpTraffic parseReverse(String reverse, int nexti){
\r
382 HttpTraffic reverseTraffic = new HttpTraffic();
\r
383 reverseTraffic.isRequest = false;
\r
384 reverseTraffic.ID = getID();
\r
385 //String[] lines = reverse.split("\\r\\n", -1);
\r
386 String[] lines = reverse.split("\\n", -1);
\r
387 int lineCount = lines.length;
\r
395 // Read the first line, and figure out response code, message.
\r
396 while (i<lineCount){
\r
397 if (line.startsWith("HTTP/1.1")){
\r
403 String[] tokens = line.split(" ", 3);
\r
404 String HTTP11 = tokens[0];
\r
405 reverseTraffic.responseCode = Integer.parseInt(tokens[1]);
\r
406 reverseTraffic.message = killTrailingWS(tokens[2]);
\r
407 i++; // done reading first line. Bump past first line.
\r
409 //if (forwardResult.message.equals("OK")){
\r
410 // return forwardResult;
\r
413 // Now read headers until we are ready for payload or parts.
\r
414 i = readHeaders(reverseTraffic, lines, i);
\r
417 if ( (i<lines.length-1) && (reverseTraffic.contentLength==0) ) {
\r
418 //there are more lines, but content-length header was zero,
\r
419 // this means we are getting keep-alive bunches of DELETEs or OKs back.
\r
420 System.err.println("###### extra responses in this one."+id);
\r
421 String filename = getID()+".reponses";
\r
422 saveFile("./xml", filename, reverse);
\r
423 reverseTraffic.extra = true;
\r
424 return reverseTraffic;
\r
427 // We are past headers now. The rest of message is payloads.
\r
428 i = readPayloads(reverseTraffic, lines, i); //messes with forwardResult and places parts in it.
\r
429 reverseTraffic.nexti = i;
\r
431 reverseTraffic.nexti = -1;
\r
433 return reverseTraffic;
\r
436 private List<HttpTraffic> handleTcpDump(String dump){
\r
439 List<HttpTraffic> trafficList = new ArrayList<HttpTraffic>();
\r
442 HttpTraffic forward = parseForward(dump, i);
\r
443 if (forward==null) break;
\r
445 forward.ID = ""+trafficID;
\r
446 if (forward.payloads.size()>0){
\r
447 saveForwardFiles(forward);
\r
449 trafficList.add(forward);
\r
451 HttpTraffic reverse = parseReverse(dump, i);
\r
452 if (reverse==null) break;
\r
453 reverse.ID = ""+trafficID;
\r
455 if (reverse.payloads.size()>0){
\r
456 saveReverseFiles(reverse);
\r
458 trafficList.add(reverse);
\r
460 return trafficList;
\r
463 public static File saveFile(String dir, String relativeName, String content){
\r
464 File result = null;
\r
465 PrintWriter writer;
\r
467 result = new File(dir, relativeName);
\r
468 writer = new PrintWriter(new FileOutputStream(result));
\r
469 }catch (Exception e){
\r
470 System.out.println("Can't write to file in saveFile: " + relativeName + " \r\n" + e);
\r
473 writer.write(content);
\r
478 private void saveForwardFiles(HttpTraffic fr){
\r
479 for (Part part : fr.payloads){
\r
480 String body = part.buffer.toString();
\r
481 if (body.trim().length()==0){
\r
484 String filename = fr.ID+'_'+fr.method+'_'+fr.url.replaceAll("/", "_")+'_'+part.label+".xml";
\r
485 filename = filename.replaceAll("/", "_");
\r
486 System.out.println("trying to save file: "+filename+" :: "+fr);
\r
487 part.filename = filename;
\r
488 saveFile("./xml", filename, body);
\r
492 private void saveReverseFiles(HttpTraffic fr){
\r
493 for (Part part : fr.payloads){
\r
494 String body = part.buffer.toString();
\r
495 if (body.trim().length()==0){
\r
498 String filename = fr.ID+'_'+fr.method+'_'+fr.url.replaceAll("/", "_");
\r
499 if (part.label.length()==0){
\r
500 if (body.trim().startsWith("<?xml")){
\r
501 filename = filename + "_res.xml";
\r
502 part.filetype = "xml";
\r
504 filename = filename + "_res.txt";
\r
505 part.filetype = "txt";
\r
508 filename = filename + '_'+part.label+"_res.xml";
\r
509 part.filetype = "xml";
\r
511 filename = filename.replaceAll("/", "_");
\r
512 System.out.println("trying to save file: "+filename+" :: "+fr);
\r
513 part.filename = filename;
\r
514 saveFile("./xml", filename, body);
\r
518 public static String readFile(String dir, String relPath) throws Exception{
\r
519 File theFile = new File(dir, relPath);
\r
520 FileInputStream fis = new FileInputStream(theFile);
\r
521 byte[] theData = new byte[(int) theFile.length()];
\r
522 // need to check the number of bytes read here
\r
523 int howmany = fis.read(theData);
\r
525 return new String(theData);
\r
528 public static List<HttpTraffic> process(String httpSessionTraffic){
\r
529 PayloadLogger pll = new PayloadLogger();
\r
530 List<HttpTraffic> trafficList = pll.handleTcpDump(httpSessionTraffic);
\r
531 return trafficList;
\r
534 public static void main(String[]args) throws Exception {
\r
535 String dump = readFile(".", args[0]);
\r
536 PayloadLogger pll = new PayloadLogger();
\r
537 List<HttpTraffic> trafficList = pll.handleTcpDump(dump);
\r
538 reporter.finished(trafficList);
\r