Free WYSIWYG HTML Editors, Web page Builder Tools

Tuesday, March 24, 2009

In the past when I was looking for free/open source HTML editors, what separated me from searching for a complete HTML editor toolkit or a Web page builder where I can key in the HTML text, add images, forms, edit a HTML page, save and publish it was the keyword WYSIWYG (What You See Is What You Get), I know most of you would have known this term which is just simple, with respect to HTML, whatever content you add or edit, say text, image, form, table, etc is what you will see or in other words a software which will hide HTML code details from you when creating your own web page with different components, enabling you to create your own website in minutes!

It goes without saying that I use WYSIWYG editors to keep it simple, although HTML standards across browsers and validity should never be compromised, but with advancements in WYSIWYG editors with sophisticated features, there is a compelling reason to use them and of course if you need to maintain HTML standards, you might need to check/tweak the generated code to confirm to the standards, from my experience, most of the times the code it generates is HTML transitional and works across different browser platforms, so here are my favorite ones.

1. Amaya
2. SeaMonkey
3. Kompozer
4. Trellian

1. Amaya

A highly effective open source cross platform WYSIWYG editor with rich features to make HTML page creation simple, the screenshot says it all, I am impressed with the placement of the buttons needed to format HTML at the right corner (reminds me of the days where I worked in Visual Basic), which is convenient to use for quick, effective formatting of the web page, the more you use it and learn, the less time it takes to create your dream HTML page, great effort!


I am listing this because its is a convenient tool which you can start with in any platform without much work as its available as a Mozilla platform suite in many UNIX flavors by default (of course you need to download it for Windows), infact I use this extensively in Solaris when preparing product documentations, how-to pages and I should say it lived upto my expectations, it prompts me for updates regularly which means a lot of development and bug fixing activity is going on there and why not, its powered by Mozilla, I will add this to my kitty as its an ever reliable tool.

3. Kompozer

A lightweight HTML WYSIWYG editor which will be productive for you when you create your own site, every tool has an unique feature which distinguishes itself and for Kompozer, its the Nvu Site Manager in the left corner where you can create your own folders/HTML files for your website, manage, edit and publish them, interesting to know that its powered by Mozilla, but its also evident from some of the menu items, which are similar to that of SeaMonkey.


Trellian is a powerful WYSIWYG editor for Windows which I use at times, it also comes with an SEO toolkit which may be useful for bloggers and I need to explore more about that, given that search engines like blogs as they are updated frequently, you always need to be search engine friendly, one more thing, be sure that you read their FAQ before proceeding with the installation.

Running Unix or Linux Commands from C, Java, Python and getting the Output String and Status code

Thursday, March 19, 2009

I saw some comments where people asked about running Unix/Linux commands from C and Java, its quite easy to do that, although I did it long time before in C, but executing UNIX commands from java and python is something which I do quite often and thought its worth posting these simple code here, doing it from java may sound quite simple, but I really tinkered with the java InputStreams and Readers for sometime before getting it right.

I tried these code from Solaris and Linux, the C and java code will work for Windows executables and commands (from java, you need a different way to execute DOS commands, but you can try other executables), the python solution is quite simple using the commands module which has methods to execute and fetch the command output/status code from UNIX, though the commands module cannot be used to run Windows executables.

All the three programs take the Unix command to be executed as a command line parameter and fetches the command output and status in a similar format, I will go from the toughest to the easiest, from C to java to python.

1. Running Unix or Linux commands from C
2. Running Unix or Linux commands from Java
3. Running Unix or Linux commands from Python

1. Running Unix or Linux commands from C

unix_command.c
 1: /*
2: * unix_command.c
3: * Author: S.Prasanna
4: * @version 1.00
5: */
6:
7: #include <stdio.h>
8: #include <stdlib.h>
9:
10: int main(int argv, char ** argc) {
11:
12: if (argv != 2) {
13: printf("Usage: unix_command \"COMMAND TO BE EXECUTED IN QUOTES\"");
14: exit(1);
15: }
16:
17: char *COMMAND = argc[1],*readLine, *tmp, *commandResult = "";
18: FILE * fp;
19: int status;
20:
21: fp = popen(COMMAND, "w");
22:
23: if (fp == NULL) {
24: perror("Command execution failed");
25: exit(1);
26: }
27:
28: printf("Printing the command output....");
29: while ((fscanf(fp, "%s", &readLine)) != EOF) {
30: tmp = (char *) realloc(commandResult, strlen(readLine));
31: commandResult = tmp;
32: strcpy(commandResult, readLine);
33: }
34: printf("\nCommand %s output = %s", COMMAND, commandResult);
35: status = pclose(fp);
36: printf ("Command %s exit status code = %d\n", COMMAND, status);
37: return status;
38: }
Output:

bash-3.00# ./unix_command.out "ls -l"
Printing the command output....
Command ls -l output = total 28
-rwxrwxrwx 1 root root 954 Mar 18 22:39 unix_command.c
-rw-r--r-- 1 root root 1598 Mar 18 21:35 unix_command.class
-rwxrwxrwx 1 root root 1321 Mar 18 21:33 unix_command.java
-rwxr-xr-x 1 root root 7408 Mar 18 22:40 unix_command.out
-rw-r--r-- 1 root root 438 Mar 18 21:33 unix_command.py
Command ls -l exit status code = 0
bash-3.00#

Explanation:

The C Code execution is quite simple, the popen() call in line 21 takes the process name or UNIX command to be executed with parameters in the form of a string and returns a file pointer (line 21) through which the process's input stream can be accessed and read (using fscanf function in line 29). The realloc() function (line 30) is used to expand the string array used to fetch the process's output as and when the input stream is read and the process's exit status code is fetched from the plose() call (line 35).

2. Running Unix or Linux commands from Java

unix_command.java
 1: /*
2: * unix_command.java
3: * Author: S.Prasanna
4: * @version 1.00
5: */
6:
7: import java.io.InputStream;
8:
9: public class unix_command {
10:
11: public static void main (String args[]) {
12: if (args.length < 1) {
13: System.out.println("Usage: java unix_command \"COMMAND TO BE EXECUTED\"");
14: System.exit(1);
15: }
16:
17: String COMMAND = args[0];
18:
19: for (int i = 1; i < args.length; i++) {
20: COMMAND = COMMAND + " " + args[i];
21: }
22:
23: InputStream in = null;
24: Process p = null;
25:
26: try {
27:
28: StringBuffer commandResult = new StringBuffer();
29: String line = null;
30: int readInt;
31:
32: p = Runtime.getRuntime().exec(args, null);
33: int returnVal = p.waitFor();
34: if (returnVal == 0)
35: in = p.getInputStream();
36: else
37: in = p.getErrorStream();
38:
39: while ((readInt = in.read()) != -1)
40: commandResult.append((char)readInt);
41:
42: System.out.println("Command " + COMMAND + " output = \n" + commandResult.toString());
43: System.out.println("Command " + COMMAND + " exit status = " + returnVal);
44: in.close();
45: } catch (Exception e) {
46: System.out.println("An exception occured while executing command " + COMMAND);
47: e.printStackTrace();
48: }
49: }
50: }
bash-3.00# java unix_command ls -l
Command ls -l output =
total 28
-rwxrwxrwx 1 root root 954 Mar 18 22:39 unix_command.c
-rw-r--r-- 1 root root 1598 Mar 18 21:35 unix_command.class
-rwxrwxrwx 1 root root 1321 Mar 18 21:33 unix_command.java
-rwxr-xr-x 1 root root 7408 Mar 18 22:40 unix_command.out
-rw-r--r-- 1 root root 438 Mar 18 21:33 unix_command.py

Command ls -l exit status = 0
bash-3.00#

Explanation:

The Runtime class's exec method (through the Runtime's static instance) takes an array of String with the process or unix command's name and its arguments and returns a Process object (line 32) whose input stream can be accessed and read through a standard java InputStream (line 35). The Process object's waitFor method (line 33) returns the exit value of the subprocess and for non successful return values (return value other than zero), the Process's ErrorStream is accessed to read the error message (line 37).

3. Running Unix or Linux commands from Python

unix_command.py
1.  #
2. # Executing UNIX commands from python
3. # unix_command.py
4. # Author: S.Prasanna
5. #
6.
7. import commands
8. import sys
9.
10. if len(sys.argv) != 2:
11. print "Usage: unix_command \"COMMAND TO BE EXECUTED IN QUOTES\""
12. sys.exit(1)
13.
14. COMMAND = sys.argv[1]
15.
16. command_result = commands.getstatusoutput(COMMAND)
17. print "Command %s exit status code = %s" % (COMMAND, command_result[0])
18. print "Command %s command result = %s" % (COMMAND, command_result[1])
Output:

bash-3.00# python unix_command.py "ls -l"
Command ls -l exit status code = 0
Command ls -l command result = total 28
-rwxrwxrwx 1 root root 954 Mar 18 22:39 unix_command.c
-rw-r--r-- 1 root root 1598 Mar 18 21:35 unix_command.class
-rwxrwxrwx 1 root root 1321 Mar 18 21:33 unix_command.java
-rwxr-xr-x 1 root root 7408 Mar 18 22:40 unix_command.out
-rw-r--r-- 1 root root 438 Mar 18 21:33 unix_command.py
bash-3.00#

Explanation:

The python solution is quite simple, the commands module's getstatusoutput method (line 16) fetches the UNIX command's exit status code and the command output string in a tuple which is printed (line 17 and 18).

Firefox and Thunderbird Troubleshooting: Firefox or Thunderbird is already running, but not responding Error

Saturday, March 14, 2009

I get these errors when invoking Firefox & Thunderbird from my individual desktop sessions on a server from two different systems, as obvious, though they are different sessions, its the same user login which points to the same NFS mounted home directory, I usually keep my Firefox and Thunderbird sessions open on both the systems and so that I can continue from where I left in the first system on the other one, when I first started Firefox and Thunderbird on my second desktop session on the second system after keeping them open in the first system (and locking it of course), I got the error

"Firefox is already running, but not responding. To open a new window you must close the existing Firefox process, or restart your system"

and

"Thunderbird is already running, but not responding. To open a new window you must close the existing thunderbird process, or restart your system"

Though I saw the above errors in Windows too, the solution is very simple, you need to remove some lock files (.parentlock and lock on Solaris and parent.lock in Windows in the firefox profile directory), you can also end any Firefox process in task manager in Windows if you get this error and restart it again.

But I wanted to explore more, how session locks work in Firefox, how can I continue with the same session I left on one system from the other, though removing lock files is part of the solution, I found some simple things about how Firefox handles sessions, knowing this might help you to troubleshoot your problem with Firefox sessions if any, also this applies to Solaris, but similar procedure should work for other flavors of Linux and Thunderbird as well.

1. Firefox Profile directory
2. Locks before and after Firefox is invoked
3. Trying to open Firefox on a different system with same login when an instance is already running/crashed on your other session
4. Clearing locks and restoring/starting your new Firefox session

1. Firefox Profile directory

Before you open a Firefox session, your firefox profile directory would be like the one shown below, for a user named solarisuser, this would be like,
bash-3.00$ pwd
/home/solarisuser

bash-3.00$ cd .mozilla/firefox
bash-3.00$ ls -la
total 18
drwxr-xr-x 3 solarisuser staff 512 Jan 24 20:40 .
drwx------ 14 solarisuser staff 1024 Jan 27 14:31 ..
drwx------ 5 solarisuser staff 1024 Mar 14 00:50 a43y8e05.default
-rw-r--r-- 1 solarisuser staff 94 Jan 24 20:39 profiles.ini

bash-3.00$ cat profiles.ini
[General]
StartWithLastProfile=1

[Profile0]
Name=default
IsRelative=1
Path=a43y8e05.default
As shown above, the firefox profile details are stored in <HOME>/.mozilla/firefox/profiles.ini which has the path to the profile directory, where <HOME> is the home directory of the user.

2. Locks before and after Firefox is invoked

Before I invoked Firefox on my session in my first system, the profile directory had the following lock files.
bash-3.00$ cd a43y8e05.default/
bash-3.00$ pwd
/home/solarisuser/.mozilla/firefox/a43y8e05.default
bash-3.00$ ls -la
total 7304
drwx------ 5 solarisuser staff 1024 Mar 14 00:50 .
drwxr-xr-x 3 solarisuser staff 512 Jan 24 20:40 ..
-rw-r--r-- 1 solarisuser staff 0 Mar 14 00:49 .parentlock
Note that there is a .parentlock file even before I invoked my Firefox instance, but this will be updated by a new .parentlock file which will be created when a user starts Firefox as can be seen from its updated timestamp below, therefore after Firefox is invoked, the profile contents would be like the one shown below.
bash-3.00$ ls -la
total 7306
drwx------ 5 solarisuser staff 1024 Mar 14 00:54 .
drwxr-xr-x 3 solarisuser staff 512 Jan 24 20:40 ..
-rw-r--r-- 1 solarisuser staff 0 Mar 14 00:54 .parentlock
lrwxrwxrwx 1 solarisuser staff 21 Mar 14 00:54 lock -> 10.5.150.5:+61310
Note the updated timestamp on the .parentlock file and a new link file lock which is created pointing to IP address and port details (probably the client IP and port details from which the Firefox instance was invoked), therefore irrespective of an already existing .parentlock, each time it will be updated when one invokes Firefox as shown above.

3. Trying to open Firefox on a different system with same login when an instance is already running/crashed on your other session

Now when I logged in to my second desktop and tried to open Firefox when an instance is already running on the first system, I got the above error as shown in the figure below, this applies for cases where an instance is still running in the first system or it crashed abruptly.


Fig 1: Firefox is already running, but not responding error

4. Clearing locks and restoring/starting your new Firefox session

Now from the second system, I removed the .parentlock file from my Firefox profile folder to get rid of the above error and start Firefox.
bash-3.00$ ls -la
total 7306
drwx------ 5 solarisuser staff 1024 Mar 14 00:54 .
drwxr-xr-x 3 solarisuser staff 512 Jan 24 20:40 ..
-rw-r--r-- 1 solarisuser staff 0 Mar 14 00:54 .parentlock
lrwxrwxrwx 1 solarisuser staff 21 Mar 14 00:54 lock -> 10.5.150.5:+61310

bash-3.00$ pwd
/home/solarisuser/.mozilla/firefox/a43y8e05.default
bash-3.00$ rm .parentlock

bash-3.00$ ls -la
total 7308
drwx------ 5 solarisuser staff 1024 Mar 14 01:02 .
drwxr-xr-x 3 solarisuser staff 512 Jan 24 20:40 ..
-rw-r--r-- 1 solarisuser staff 0 Mar 14 00:54 .nfs6D1F81
lrwxrwxrwx 1 solarisuser staff 21 Mar 14 00:54 lock -> 10.5.150.5:+61310
Now after I removed the .parentlock, Firefox created a file called .nfs6D1F81, this is because Firefox has to save the session details of the instance which is already running because the .parentlock file is abruptly removed ( it may be a case similar to that of Firefox crashing), in other words, the presence of the above .nfs6D1F81 file is an indication that the session is saved by Firefox for restoring.

Then after I invoked Firefox in my second system (on a different session from the first one), it prompted me on whether I should restore the previous session or open a new session as shown below.

Fig 2: Restoring/Opening a new Firefox session

Now since I want to continue from where I left, I selected the restore session option, in this way I can work on the same Firefox session from my second system login. After the second session is opened, the profile directory contents are shown below.
bash-3.00$ ls -la
total 7312
drwx------ 5 solarisuser staff 1024 Mar 14 01:16 .
drwxr-xr-x 3 solarisuser staff 512 Jan 24 20:40 ..
-rw-r--r-- 1 solarisuser staff 225 Mar 14 00:50 .nfs422F81
-rw-r--r-- 1 solarisuser staff 0 Mar 14 00:54 .nfs6D1F81
-rw-r--r-- 1 solarisuser staff 0 Mar 14 01:14 .parentlock
lrwxrwxrwx 1 solarisuser staff 21 Mar 14 01:14 lock -> 10.5.150.5:+16851
As obvious a new .parentlock file is created after the invocation as can be seen from its updated timestamp and also a different lock is created for the second instance (see the difference between the two lock files shown above), but now you also see two files .nfs422F81 and .nfs6D1F81, the second file is the one we saw after we removed the .parentlock file and a new one is created for the second invocation, my guess is since there are two instances running now with one which is active (in the second system) and the other process still running in the other system (in the first system), probably the two .nfs files are there to identify different sessions running on the two instances (so that they can be saved when any one of the instance crashes), therefore now the user can run Firefox from both the systems.

Finally after I closed the Firefox on both the systems, now I no longer see the .nfs files and the lock link file removed, probably an indication that the sessions are closed gracefully.
bash-3.00$ ls -la
total 7306
drwx------ 5 solarisuser staff 1024 Mar 14 01:21 .
drwxr-xr-x 3 solarisuser staff 512 Jan 24 20:40 ..
-rw-r--r-- 1 solarisuser staff 0 Mar 14 01:21 .parentlock
The same logic will apply for Thunderbird as well (<HOME>/.thunderbird), little things teach us a lot!.

A Standalone Java HTTP File Upload Server for handling large text and binary files

Tuesday, March 10, 2009

After I published a code in java for handling file uploads using HTTP POST method, I got several requests for extending the same code to handle binary files like images, executables, etc, in fact I saw some value in extending that code to handle binary files as well as its convenient for me to upload some of my files from one machine to other (in my work) for which I needed an easy lightweight solution, hence this implementation.

In fact its not as easy as I expected, though the implementation is quite simple, but the scalability of this file upload server is not, when I tested it initially with files of small size (less than 10 MB), it used to work perfectly, but for large files, I saw frequent connection resets from browsers especially when the file size is more than 100MB (where I needed to refresh the page again for the upload to work) which straightaway motivated me to improve the scalability of this server than accepting it as a limitation and it was a great learning experience indeed on researching the factors which affects the scalability of this HTTP POST Server implementation with sockets.

Though this file upload server in java can upload files of unlimited size, but you need to tune some parameters accordingly, I have found the buffer size settings of TCP Sockets as one and the next is having the Server Thread to sleep for small intervals of time (in milli seconds) before every receive call so that the server can process large input streams from the client without a processing speed mismatch resulting in connection resets, we will see more about this in the explanation of the code below.

Listing 1: HTTPFileUploadServer.java

 1: /*
2: * HTTPFileUploadServer.java
3: * Author: S.Prasanna
4: * @version 1.00
5: */
6:
7: // A File upload server which will handle files of any type and size
8: // (Text as well as binary files including images)
9:
10: import java.io.*;
11: import java.net.*;
12: import java.util.*;
13:
14: public class HTTPFileUploadServer extends Thread {
15:
16: static final String HTML_START =
17: "<html>" +
18: "<title>HTTP POST Server in java</title>" +
19: "<body>";
20:
21: static final String HTML_END =
22: "</body>" +
23: "</html>";
24:
25: Socket connectedClient = null;
26: DataInputStream inFromClient = null;
27: DataOutputStream outToClient = null;
28:
29: public HTTPFileUploadServer(Socket client) {
30: connectedClient = client;
31: }
32:
33: void closeStreams() throws Exception {
34: inFromClient.close();
35: outToClient.close();
36: connectedClient.close();
37: }
38:
39: // A routine to find the POST request end string from the
40: // Inputstream
41: int sub_array(byte [] array1, byte [] array2) throws Exception {
42:
43: int i = array1.length - 1;
44: int j = array2.length - 1;
45: boolean found = false;
46:
47: for (int k = i; k >=0; k--) {
48: if (array1[k] == array2[j]) {
49: found = true;
50: for (int l = j - 1; l >=0; l--) {
51: k = k - 1;
52: if (k < 0) return -1;
53: if (array1[k] == array2[l]) continue;
54: else {found = false; break;}
55: }
56: if (found == true) return k;
57: }
58: }
59: return -1;
60: }
61:
62: // Read from InputStream
63: public String readLine() throws Exception {
64: String line = "";
65:
66: char c = (char) inFromClient.read();
67:
68: while (c != '\n'){
69: line = line + Character.toString(c);
70: c = (char) (inFromClient.read());
71: }
72: return line.substring(0,line.lastIndexOf('\r'));
73: }
74:
75: //Thread for processing individual clients
76: public void run() {
77:
78: String currentLine = null, postBoundary = null,
79: contentength = null, filename = null, contentLength = null;
80: FileOutputStream fout = null;
81:
82: // Change these two parameters depending on the size of the file to be uploaded
83: // For a very large file > 200 MB, increase THREAD_SLEEP_TIME to prevent connection
84: // resets during upload, current settings are good tor handling upto 100 MB file size
85: long THREAD_SLEEP_TIME = 20;
86: int BUFFER_SIZE = 65535;
87:
88: // Upload File size limit = 25MB, can be increased
89: long FILE_SIZE_LIMIT = 25000000;
90:
91: try {
92:
93: System.out.println( "The Client "+
94: connectedClient.getInetAddress() + ":" + connectedClient.getPort() + " is connected");
95:
96: inFromClient = new DataInputStream(connectedClient.getInputStream());
97: outToClient = new DataOutputStream(connectedClient.getOutputStream());
98:
99: connectedClient.setReceiveBufferSize(BUFFER_SIZE);
100:
101: currentLine = readLine();
102: String headerLine = currentLine;
103: StringTokenizer tokenizer = new StringTokenizer(headerLine);
104: String httpMethod = tokenizer.nextToken();
105: String httpQueryString = tokenizer.nextToken();
106:
107: System.out.println(currentLine);
108:
109: if (httpMethod.equals("GET")) { // GET Request
110: System.out.println("GET request");
111: if (httpQueryString.equals("/")) {
112: // The default home page
113: String responseString = HTTPFileUploadServer.HTML_START +
114: "<form action=\"http://127.0.0.1:5000\" enctype=\"multipart/form-data\"" +
115: "method=\"post\">" +
116: "Enter the name of the File <input name=\"file\" type=\"file\"><br>" +
117: "<input value=\"Upload\" type=\"submit\"></form>" +
118: "Upload only text files." +
119: HTTPFileUploadServer.HTML_END;
120: sendResponse(200, responseString , false);
121: } else {
122: sendResponse(404, "<b>The Requested resource not found ...." +
123: "Usage: http://127.0.0.1:5000", false);
124: }
125:
126: } //if
127: else { //POST Request
128:
129: System.out.println("POST request");
130: while(true) {
131: currentLine = readLine();
132:
133: if (currentLine.indexOf("Content-Type: multipart/form-data") != -1) {
134: postBoundary = currentLine.split("boundary=")[1];
135: // The POST boundary
136:
137: while (true) {
138: currentLine = readLine();
139: if (currentLine.indexOf("Content-Length:") != -1) {
140: contentLength = currentLine.split(" ")[1];
141: System.out.println("Content Length = " + contentLength);
142: break;
143: }
144: }
145:
146: //Content length should be <= 25MB
147: if (Long.valueOf(contentLength) > FILE_SIZE_LIMIT) {
148: inFromClient.skip(Long.valueOf(contentLength));
149: sendResponse(200, "File size should be less than 25MB", false);
150: break;
151: }
152:
153: while (true) {
154: currentLine = readLine();
155: System.out.println(currentLine);
156: if (currentLine.indexOf("--" + postBoundary) != -1) {
157: filename = readLine().split("filename=")[1].replaceAll("\"", "");
158: String [] filelist = filename.split("\\" + System.getProperty("file.separator"));
159: filename = filelist[filelist.length - 1];
160: filename = filename.trim();
161: break;
162: }
163: }
164:
165: if (filename.length() == 0) {
166: System.out.println("No input file selected.");
167: sendResponse(200, "Please select a valid file to upload..", false);
168: break;
169: }
170:
171: String fileContentType = null;
172:
173: try {
174: fileContentType = readLine().split(" ")[1];
175: } catch (Exception e) {
176: System.out.println("Can't determine POST request length");
177: }
178:
179: System.out.println("File content type = " + fileContentType);
180:
181: readLine(); //assert(readLine(inFromClient).equals("")) : "Expected line in POST request is "" ";
182: fout = new FileOutputStream(filename);
183:
184: byte [] buffer = new byte[BUFFER_SIZE], endarray;
185: String end_flag = "--" + postBoundary + "--";
186:
187: endarray = end_flag.getBytes();
188:
189: int bytesRead, bytesAvailable;
190:
191: while ((bytesAvailable = inFromClient.available()) > 0) {
192:
193: Thread.sleep(THREAD_SLEEP_TIME);
194:
195: //System.out.println("Available = " + inFromClient.available());
196: bytesRead = inFromClient.read(buffer, 0, BUFFER_SIZE);
197:
198: int end_byte = 0;
199:
200: //When number of bytes to be read in the stream <>
201: if (bytesAvailable < BUFFER_SIZE) {
202:
203: //System.out.println("End array length =" + endarray.length);
204: //System.out.println("Bytes read = " + bytesRead);
205:
206: // Case where part of POST Boundary comes in the last buffer
207: if (bytesAvailable < endarray.length) {
208: byte [] extendedArray = new byte[BUFFER_SIZE + bytesAvailable];
209: System.arraycopy(buffer, 0, extendedArray, 0, bytesRead);
210: bytesRead = inFromClient.read(extendedArray, BUFFER_SIZE, bytesAvailable);
211: end_byte = sub_array(extendedArray, endarray);
212: if (end_byte == -1) fout.write(buffer, 0, bytesRead);
213: else fout.write(extendedArray, 0, end_byte - 2);
214: }
215: else {
216: // Case where POST Boundary is part of last buffer
217: end_byte = sub_array(buffer, endarray);
218: System.out.println("End byte = " + end_byte);
219: if (end_byte == -1) fout.write(buffer, 0, bytesRead);
220: else fout.write(buffer,0, end_byte - 2);
221: }
222: } else {
223: // Case where POST Boundary is part of the full buffer
224: if (bytesAvailable == 65535) end_byte = sub_array(buffer, endarray);
225: else end_byte = sub_array(buffer, endarray);
226: if (end_byte == -1) fout.write(buffer, 0, bytesRead);
227: else fout.write(buffer,0, end_byte - 2);
228: }
229: }//while
230:
231: sendResponse(200, "File " + filename + " Uploaded..", false);
232: fout.close();
233: break;
234: } //if
235: }//while (true); //End of do-while
236: }//else
237: //Close all streams
238: System.out.println("Closing All Streams....");
239: closeStreams();
240: } catch (Exception e) {
241: e.printStackTrace();
242: }
243: System.out.println("Done....");
244: }
245:
246: public void sendResponse(int statusCode, String responseString, boolean isFile) throws Exception {
247:
248: String statusLine = null;
249: String serverdetails = "Server: Java HTTPServer";
250: String contentLengthLine = null;
251: String fileName = null;
252: String contentTypeLine = "Content-Type: text/html" + "\r\n";
253: FileInputStream fin = null;
254:
255: if (statusCode == 200)
256: statusLine = "HTTP/1.1 200 OK" + "\r\n";
257: else
258: statusLine = "HTTP/1.1 404 Not Found" + "\r\n";
259:
260: if (isFile) {
261: fileName = responseString;
262: fin = new FileInputStream(fileName);
263: contentLengthLine = "Content-Length: " + Integer.toString(fin.available()) + "\r\n";
264: if (!fileName.endsWith(".htm") && !fileName.endsWith(".html"))
265: contentTypeLine = "Content-Type: \r\n";
266: }
267: else {
268: responseString = HTTPFileUploadServer.HTML_START + responseString + HTTPFileUploadServer.HTML_END;
269: contentLengthLine = "Content-Length: " + responseString.length() + "\r\n";
270: }
271:
272: outToClient.writeBytes(statusLine);
273: outToClient.writeBytes(serverdetails);
274: outToClient.writeBytes(contentTypeLine);
275: outToClient.writeBytes(contentLengthLine);
276: outToClient.writeBytes("Connection: close\r\n");
277: outToClient.writeBytes("\r\n");
278:
279: if (isFile) sendFile(fin);
280: else outToClient.writeBytes(responseString);
281: }
282:
283: //Send the requested file
284: public void sendFile(FileInputStream fin) throws Exception {
285: byte[] buffer = new byte[1024] ;
286: int bytesRead;
287:
288: while ((bytesRead = fin.read(buffer)) != -1 ) {
289: outToClient.write(buffer, 0, bytesRead);
290: }
291: fin.close();
292: }
293:
294: public static void main (String args[]) throws Exception {
295:
296: ServerSocket Server = new ServerSocket (5000, 10, InetAddress.getByName("127.0.0.1"));
297: System.out.println ("HTTP Server Waiting for client on port 5000");
298:
299: //Create a new thread for processing clients
300: while(true) {
301: Socket connected = Server.accept();
302: (new HTTPFileUploadServer(connected)).start();
303: }
304: }
305: }
Explanation:

Well, this is a large chunk of code and most part of it are similar to the HTTP POST Server for uploading text files we have seen in an earlier post, the additional things required for handling binary files are

1. Two parameters THREAD_SLEEP_TIME (line 85)and BUFFER_SIZE (line 86) which need to be tuned for handling file uploads of different size, for large file uploads increase the THREAD_SLEEP_TIME so that the connection between the server and the client won't be reset due to stream processing speed mismatch between client and the server, also the current settings are good for handling files of upto 100MB, even though I have restricted the default file upload size limit to 25MB (line 89).

2. Code for tracking the end of the binary stream, this can be done easily using the BufferedReader's readLine method for text files, but for binary files, this method doesn't convert bytes to String properly, therefore we need to process the stream in a byte array (line 184) and check if the POST boundary string (used to indicate the end of the stream in a HTTP POST request) is reached by converting the POST boundary end string into a byte array (line 187) and comparing if the end array is a subset of the byte array read from the inputStream (function sub_array in line 41).

3. At times (very rarely) you may get Connection Reset error from your browser while uploading very large files using this code, but when you get those errors, refresh the page or try uploading again, it should be successful, also modify the parameters mentioned in (1) according to your system's processing power and memory.

4. Lines 200 - 229, here there are three cases which need to be checked for detecting the end of the stream, first we need to check if the bytes available for reading from the stream is < BUFFER_SIZE (line 201), where we can know for sure that this is the last buffer to be read and if the bytes available is less than the end stream array, that means that we read only a part of the end stream in the current buffer, therefore we need to expand the array and get the remaining bytes to track the end stream in the buffer, the second and third cases are straightforward where we just check the end stream in the read buffer to finish processing.

5. The DataInputStream's inherited available method is used to check the end of the input stream from the client (line 191).

Last but not the least, I have tested the code rigorously with simultaneous file uploads with size greater than 100 MB and it worked for me with the above settings, also I have tested this code in IE and Firefox, please feel free to report bugs in this, if any.


Copyright © 2016 Prasanna Seshadri, www.prasannatech.net, All Rights Reserved.
No part of the content or this site may be reproduced without prior written permission of the author.