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
27 import java.util.ArrayList;
\r
28 import java.util.List;
\r
30 public class PayloadLogger{
\r
32 public static void saveReport(){
\r
33 reporter.writeTable();
\r
36 private static Reporter reporter = new Reporter();
\r
38 private static volatile int ID = 1000;
\r
39 public String getID(){
\r
43 private static String DD = "--";
\r
44 private static String LABEL="LABEL: ";
\r
46 static class Reporter {
\r
48 table.append("<html><body><table border='1'>\r\n");
\r
50 public static final String START = "<tr><td>";
\r
51 public static final String SEP = "</td><td>";
\r
52 public static final String END = "</td></tr>";
\r
54 public void writeTable(){
\r
55 table.append("</table></body></html>");
\r
56 saveFile("./xml/", "results.html", table.toString());
\r
59 private StringBuffer table = new StringBuffer();
\r
61 public synchronized void finished(HttpTraffic traffic){
\r
62 String direction = traffic.isRequest ?
\r
63 "<span style='background-color: lightgreen;'>==></span>"
\r
69 .append(traffic==null ? "null" : traffic.toRow(SEP, this))
\r
73 public synchronized void finished(List<HttpTraffic> trafficList){
\r
74 for (HttpTraffic traffic: trafficList){
\r
79 public static String link(String desc, String url){
\r
80 return "<a href='"+url+"'>"+desc+"</a>";
\r
82 public static final String newline = "<br />\r\n";
\r
87 static class HttpTraffic {
\r
88 public HttpTraffic(){
\r
89 payloads = new ArrayList<Part>();
\r
91 public List<Part> payloads;
\r
92 public String method = "";
\r
93 public String url = "";
\r
94 public String queryParams = "";
\r
95 public String message = "";
\r
96 public int responseCode = 0;
\r
97 public String boundary = "";
\r
98 public String location = "";
\r
99 public long contentLength = -1;
\r
100 public boolean isRequest = true;
\r
101 public boolean extra = false;
\r
102 public String ID = "0";
\r
103 public int nexti = 0;
\r
105 public Part getPart(String label){
\r
106 for (Part part : payloads){
\r
107 if (part.label.equalsIgnoreCase(label)){
\r
114 public String toRow(String sep, Reporter reporter){
\r
115 StringBuffer b = new StringBuffer();
\r
116 for (Part part : payloads){
\r
117 String name = part.label;
\r
118 if (part.filename.length()==0){
\r
121 if (name.trim().length()<=0){
\r
124 name = name+" ("+part.filetype+')';
\r
125 String link = reporter.link(name, part.filename);
\r
127 .append(reporter.newline);
\r
129 String parts = b.toString();
\r
149 static class Part {
\r
150 public Part(String boundary){
\r
151 this.boundary = boundary;
\r
153 public boolean isMultipart(){
\r
154 return boundary.length()>0;
\r
156 public String toString(){
\r
157 return "Part:"+label+";";
\r
159 public String filename = "";
\r
160 public String filetype = "";
\r
161 public String boundary;
\r
162 public StringBuffer buffer = new StringBuffer();
\r
163 public String getContent(){
\r
164 return buffer.toString();
\r
166 public String label = "";
\r
167 public int readPart(String[]lines, int i){
\r
169 boolean readingPartHeaders = true;
\r
170 while(readingPartHeaders){
\r
171 line = killTrailingWS(lines[i]);
\r
172 if (line.toUpperCase().startsWith(LABEL)){
\r
173 this.label = line.substring(LABEL.length()).trim();
\r
174 } else if (line.trim().length()==0){
\r
175 readingPartHeaders = false;
\r
179 while (i<lines.length){
\r
181 if (line.startsWith(DD+boundary)){
\r
184 this.buffer.append(line).append("\r\n"); //todo: maybe don't add CRLF on last line.
\r
189 public int readRemaining(String [] lines, int i, long contentLength){
\r
192 while (i<lines.length && bytesRead<contentLength){
\r
193 line = killTrailingWS(lines[i]);
\r
194 if (line.startsWith("HTTP/1.1")){
\r
197 int read = line.length();
\r
199 buffer.append(line).append("\r\n"); //todo: maybe don't add CRLF on last line.
\r
206 public static String parseBoundary(String headerLine) {
\r
207 if (Tools.isEmpty(headerLine)) {
\r
210 String lineUP = headerLine.toUpperCase();
\r
211 String boundary = "";
\r
212 if (lineUP.startsWith("CONTENT-TYPE:")) {
\r
213 String[] boundaryTokens = headerLine.split("boundary=");
\r
214 if (boundaryTokens.length == 2) {
\r
215 boundary = killTrailingWS(boundaryTokens[1]);
\r
217 // Content-Type: multipart/mixed; boundary=a97c20ab-3ef6-4adc-82b0-6cf28c450faf;charset=ISO-8859-1
\r
219 String[] boundaryTerm = boundary.split(";");
\r
220 boundary = boundaryTerm[0];
\r
222 } else if (boundaryTokens.length > 2) {
\r
223 System.err.println("WARNING: too many tokens after boundary= on Content-Type: header line: " + headerLine);
\r
229 /** places the boundary on the HttpTraffic in parameter object if boundary found in header "Content-Type:".
\r
230 * @return the index of the NEXT line the caller should read. */
\r
231 protected static int readHeaders(HttpTraffic traffic, String[]lines, int i){
\r
232 int lineCount = lines.length;
\r
233 String line, lineUP;
\r
234 // Now read headers until we are ready for payload or parts.
\r
235 while (i<lineCount){
\r
237 if (line.trim().length()==0){ //blank line seen: end of headers.
\r
240 } else { //still reading outer headers.
\r
241 lineUP = line.toUpperCase().trim();
\r
242 if (lineUP.startsWith("CONTENT-TYPE:")){
\r
243 String[] boundaryTokens = line.split("boundary=");
\r
244 if (boundaryTokens.length == 2){
\r
245 traffic.boundary = killTrailingWS(boundaryTokens[1]);
\r
247 } else if (boundaryTokens.length > 2){
\r
248 System.err.println("WARNING: too many tokens after boundary= on Content-Type: header line: "+line);
\r
250 } else if (lineUP.startsWith("LOCATION: ")){
\r
251 traffic.location = killTrailingWS(line.substring("LOCATION: ".length()));
\r
252 } else if (lineUP.startsWith("CONTENT-LENGTH: ")){
\r
253 traffic.contentLength = Integer.parseInt(killTrailingWS(line.substring("CONTENT-LENGTH: ".length())));
\r
265 private static String killTrailingWS(String s){
\r
266 int i = s.length();
\r
268 char c = s.charAt(i-1);
\r
269 if (c=='\r' || c=='\n' || c==' '){
\r
276 return s.substring(0, i);
\r
279 public static HttpTraffic readPayloads(String fullPayload, String boundary, long contentLength){
\r
280 HttpTraffic traffic = new HttpTraffic();
\r
281 traffic.contentLength = contentLength;
\r
282 traffic.boundary = boundary;
\r
283 String [] lines = fullPayload.split("\\n", -1);
\r
284 readPayloads(traffic, lines, 0);
\r
288 protected static int readPayloads(HttpTraffic traffic, String[]lines, int i){
\r
289 if (traffic.boundary.length()<=0){ //END of headers, and no boundary, so read remaining and return.
\r
290 if (traffic.contentLength == 0){
\r
293 Part part = new Part("");
\r
294 traffic.payloads.add(part);
\r
295 i = part.readRemaining(lines, i, traffic.contentLength);
\r
298 int lineCount = lines.length;
\r
300 while (i<lineCount){
\r
301 //rest of message is payloads.
\r
304 if (line.startsWith( DD + traffic.boundary + DD )){ //this is the ending boundary.
\r
305 //close and accept payload chunk.
\r
306 i++; //bump past last boundary. There might be more traffic after this.
\r
308 } else if (line.startsWith(DD + traffic.boundary)){ //this is a first or middle boundary, but not last boundary.
\r
309 i++; //bump past boundary
\r
310 //begin payload chunk
\r
311 Part part = new Part(traffic.boundary);
\r
312 traffic.payloads.add(part);
\r
313 i = part.readPart(lines, i);
\r
316 //if (line.trim().length()>0){
\r
317 // System.err.println("********** Skipping line: "+line); //either parser error, or something is outside of a boundary.
\r
326 private HttpTraffic parseForward(String forward, int nexti){
\r
327 HttpTraffic forwardTraffic = new HttpTraffic();
\r
328 forwardTraffic.isRequest = true;
\r
329 forwardTraffic.ID = getID();
\r
330 //String[] lines = forward.split("\\r\\n", -1);
\r
331 String[] lines = forward.split("\\n", -1);
\r
332 int lineCount = lines.length;
\r
336 // Read the first line, and figure out if GET, POST, etc., and the URI
\r
338 while (line.trim().length()==0){
\r
340 if (i>=lineCount-1){
\r
345 String[] tokens = line.split(" ", -1);
\r
346 forwardTraffic.method = tokens[0];
\r
347 String urlString = tokens[1];
\r
348 String[] urlHalves = urlString.split("\\?", -1); //look for a query string of the form /foo/bar?param=baz and break on question mark.
\r
349 forwardTraffic.url = urlHalves[0];
\r
350 if (urlHalves.length > 1){
\r
351 forwardTraffic.queryParams = urlHalves[1];
\r
355 //if (forwardTraffic.method.equals("GET")|| forwardTraffic.method.equals("DELETE")){
\r
356 // return forwardTraffic;
\r
358 // Now read headers until we are ready for payload or parts.
\r
359 i = readHeaders(forwardTraffic, lines, i);
\r
362 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
364 //there are more lines, but content-length header was zero,
\r
365 // this means we are getting keep-alive bunches of DELETEs or OKs back.
\r
366 System.err.println("###### extra requests in this one."+getID());
\r
367 String filename = getID()+'_'+forwardTraffic.method+'_'+forwardTraffic.url.replaceAll("/", "_")+".requests";
\r
368 saveFile("./xml", filename, forward);
\r
369 return forwardTraffic;
\r
373 // We are past headers now. The rest of message is payloads.
\r
374 i = readPayloads(forwardTraffic, lines, i); //messes with forwardTraffic and places parts in it.
\r
375 forwardTraffic.nexti = i;
\r
376 return forwardTraffic;
\r
379 private HttpTraffic parseReverse(String reverse, int nexti){
\r
380 HttpTraffic reverseTraffic = new HttpTraffic();
\r
381 reverseTraffic.isRequest = false;
\r
382 reverseTraffic.ID = getID();
\r
383 //String[] lines = reverse.split("\\r\\n", -1);
\r
384 String[] lines = reverse.split("\\n", -1);
\r
385 int lineCount = lines.length;
\r
393 // Read the first line, and figure out response code, message.
\r
394 while (i<lineCount){
\r
395 if (line.startsWith("HTTP/1.1")){
\r
401 String[] tokens = line.split(" ", 3);
\r
402 String HTTP11 = tokens[0];
\r
403 reverseTraffic.responseCode = Integer.parseInt(tokens[1]);
\r
404 reverseTraffic.message = killTrailingWS(tokens[2]);
\r
405 i++; // done reading first line. Bump past first line.
\r
407 //if (forwardResult.message.equals("OK")){
\r
408 // return forwardResult;
\r
411 // Now read headers until we are ready for payload or parts.
\r
412 i = readHeaders(reverseTraffic, lines, i);
\r
415 if ( (i<lines.length-1) && (reverseTraffic.contentLength==0) ) {
\r
416 //there are more lines, but content-length header was zero,
\r
417 // this means we are getting keep-alive bunches of DELETEs or OKs back.
\r
418 System.err.println("###### extra responses in this one."+id);
\r
419 String filename = getID()+".reponses";
\r
420 saveFile("./xml", filename, reverse);
\r
421 reverseTraffic.extra = true;
\r
422 return reverseTraffic;
\r
425 // We are past headers now. The rest of message is payloads.
\r
426 i = readPayloads(reverseTraffic, lines, i); //messes with forwardResult and places parts in it.
\r
427 reverseTraffic.nexti = i;
\r
429 reverseTraffic.nexti = -1;
\r
431 return reverseTraffic;
\r
434 private List<HttpTraffic> handleTcpDump(String dump){
\r
437 List<HttpTraffic> trafficList = new ArrayList<HttpTraffic>();
\r
440 HttpTraffic forward = parseForward(dump, i);
\r
441 if (forward==null) break;
\r
443 forward.ID = ""+trafficID;
\r
444 if (forward.payloads.size()>0){
\r
445 saveForwardFiles(forward);
\r
447 trafficList.add(forward);
\r
449 HttpTraffic reverse = parseReverse(dump, i);
\r
450 if (reverse==null) break;
\r
451 reverse.ID = ""+trafficID;
\r
453 if (reverse.payloads.size()>0){
\r
454 saveReverseFiles(reverse);
\r
456 trafficList.add(reverse);
\r
458 return trafficList;
\r
461 public static File saveFile(String dir, String relativeName, String content){
\r
462 File result = null;
\r
463 PrintWriter writer;
\r
465 result = new File(dir, relativeName);
\r
466 writer = new PrintWriter(new FileOutputStream(result));
\r
467 }catch (Exception e){
\r
468 System.out.println("Can't write to file in saveFile: " + relativeName + " \r\n" + e);
\r
471 writer.write(content);
\r
476 private void saveForwardFiles(HttpTraffic fr){
\r
477 for (Part part : fr.payloads){
\r
478 String body = part.buffer.toString();
\r
479 if (body.trim().length()==0){
\r
482 String filename = fr.ID+'_'+fr.method+'_'+fr.url.replaceAll("/", "_")+'_'+part.label+".xml";
\r
483 filename = filename.replaceAll("/", "_");
\r
484 System.out.println("trying to save file: "+filename+" :: "+fr);
\r
485 part.filename = filename;
\r
486 saveFile("./xml", filename, body);
\r
490 private void saveReverseFiles(HttpTraffic fr){
\r
491 for (Part part : fr.payloads){
\r
492 String body = part.buffer.toString();
\r
493 if (body.trim().length()==0){
\r
496 String filename = fr.ID+'_'+fr.method+'_'+fr.url.replaceAll("/", "_");
\r
497 if (part.label.length()==0){
\r
498 if (body.trim().startsWith("<?xml")){
\r
499 filename = filename + "_res.xml";
\r
500 part.filetype = "xml";
\r
502 filename = filename + "_res.txt";
\r
503 part.filetype = "txt";
\r
506 filename = filename + '_'+part.label+"_res.xml";
\r
507 part.filetype = "xml";
\r
509 filename = filename.replaceAll("/", "_");
\r
510 System.out.println("trying to save file: "+filename+" :: "+fr);
\r
511 part.filename = filename;
\r
512 saveFile("./xml", filename, body);
\r
516 public static String readFile(String dir, String relPath) throws Exception{
\r
517 File theFile = new File(dir, relPath);
\r
518 FileInputStream fis = new FileInputStream(theFile);
\r
519 byte[] theData = new byte[(int) theFile.length()];
\r
520 // need to check the number of bytes read here
\r
521 int howmany = fis.read(theData);
\r
523 return new String(theData);
\r
526 public static List<HttpTraffic> process(String httpSessionTraffic){
\r
527 PayloadLogger pll = new PayloadLogger();
\r
528 List<HttpTraffic> trafficList = pll.handleTcpDump(httpSessionTraffic);
\r
529 return trafficList;
\r
532 public static void main(String[]args) throws Exception {
\r
533 String dump = readFile(".", args[0]);
\r
534 PayloadLogger pll = new PayloadLogger();
\r
535 List<HttpTraffic> trafficList = pll.handleTcpDump(dump);
\r
536 reporter.finished(trafficList);
\r