Сначала следует отметить, что у мидлета есть ограничение: он может передать по HTTP файл размером не больше чем в 2Кб. Так что будем передавать файл кусочками, а потом на сервере склеивать. Кроме того, картинку следует передавать не абы как, а в кодировке
base64
, иначе, опять же, будут возникать непонятные проблемы передачи (по крайней мере, у меня возникали).
Клиент
Пусть мы каким-то образом получили файл как
byte[]
. Напишем специальный класс
Uploader
, с помощью которого будем заливать картинку на сервер. Передавать файл будем фрагментами по 1 Кб. Для каждого фрагмента будем открывать отдельный коннекшен и составлять POST-запрос, содержащий следующие данные:
- Номер фрагмента (
number
)
- Общее количество фрагментов (
count
)
- Собственно содержание (
content
) в виде вложенного файла
Добавим в наш мидлет следующие поля:
/**
* Размер передаваемого фрагмента картинки
*/
private static int CHUNK_SIZE = 1024;
/**
* Граница между разделами в HTTP-запросе
*/
private static String BOUNDARY = "42hjkiqp4279h";
/**
* Адрес, на который мы посылаем запросы
*/
private static String HOST = "http://some_url/Handler.ashx";
/**
* Сообщение, которое мы получаем в случае успешной отправки запроса
*/
private static String UPLOAD_OK_MSG = "Upload OK";
И следующие методы:
/**
* Залитие картинки на сервер
* @param src Массив символов, содержащий картинку
* @return Ответ от сервера: удалось залить, или нет
*/
public String Upload(byte[] src)
{
String response = "";
try
{
int nchunks = src.length / CHUNK_SIZE;
System.out.println("size = " + src.length + ", chunks count = " + nchunks);
String fileId = GetNextPhotoName(5);
for (int i = 0; i < nchunks; ++i)
{
String content = Base64.encode(src, i * CHUNK_SIZE, CHUNK_SIZE);
response = UploadChunk(fileId, i + 1, nchunks, content);
if (!IsUploadOk(response))
{
break;
}
}
System.out.println("Uploading file " + fileId + ": " + response);
}
catch (Exception ex)
{
System.out.println(ex.getClass().getName() + " while uploading file: " + ex.getMessage());
}
finally
{
}
return response;
}
/**
* Формирование раздела HTTP-запроса, содержащего значение некоторого параметра
* @param name Имя параметра
* @param value Значение параметра
* @return Строка - раздел HTTP-запроса
*/
private static String FormData(String name, Object value)
{
return "--" + BOUNDARY + "\r\n" +
"Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n" + value + "\r\n";
}
/**
* Заливает на сервер фрагмента картинки
* @param fileId Идентификатор картинки
* @param number Номер фрагмента
* @param count Общее количество фрагментов
* @param content Содержаение фрагмента
* @return Ответ от сервера: удалось залить или нет
*/
private String UploadChunk(String fileId, int number, int count, String content)
{
String message =
FormData("number", new Integer(number)) +
FormData("count", new Integer(count)) +
FormData("sessionId", sessionId) +
"--" + BOUNDARY + "\r\n" +
"Content-Disposition: form-data; name=\"imagefile\"; filename=\"" + fileId + "\"\r\n" +
"Content-Transfer-Encoding: base64\r\n" +
"Content-Type: image/png\r\n\r\n" + content + "\r\n" +
"--" + BOUNDARY + "--\r\n";
String response = SendPostData(message);
System.out.println("Chunk " + fileId + "_" + number + " " + response);
return response;
}
/**
* Отправляет на сервер POST-запрос
* @param message Текст запроса
* @return Ответ сервера
*/
private static String SendPostData(String message)
{
HttpConnection conn = null;
InputStream is = null;
OutputStream os = null;
String response = null;
try
{
conn = (HttpConnection) Connector.open(HOST);
conn.setRequestMethod(HttpConnection.POST);
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
byte[] messageBody = message.getBytes();
conn.setRequestProperty("Content-Length", Integer.toString(messageBody.length));
os = conn.openOutputStream();
os.write(messageBody);
int rc = conn.getResponseCode();
if (rc != HttpConnection.HTTP_OK)
{
throw new IOException("HTTP responce code: " + rc);
}
is = conn.openInputStream();
int ch;
StringBuffer data = new StringBuffer();
while ((ch = is.read()) != -1)
{
data.append((char) ch);
}
response = data.toString();
}
catch (Exception e)
{
response = "Error while connecting:\n\n" + e.getMessage();
}
finally
{
try
{
if (is != null)
{
is.close();
}
if (os != null)
{
os.close();
}
if (conn != null)
{
conn.close();
}
}
catch (Exception e)
{
}
}
return response;
}
/**
* Проверка, успешно ли произошел Upload на сервер
* @param response Ответ сервера
* @return
*/
private boolean IsUploadOk(String response)
{
return response.equals(UPLOAD_OK_MSG);
}
Сервер
Сервер, как и раньше, написан на .NET. Есть хэндлер
Handler.ashx
, и в нем написано следующее:
namespace MobileServer
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Handler : IHttpHandler
{
private const string UPLOAD_PATH = "...";
private static string GetChunkFileName(string fileName, int number)
{
return String.Format("{0}\{1}_{2}", UPLOAD_PATH, f.FileName, number);
}
private static string GetFileName(string fileName)
{
return String.Format("{0}\{1}.png", UPLOAD_PATH, f.FileName);
}
private static void SaveChunkToFile(int number, HttpPostedFile f)
{
byte[] buf = new byte[f.ContentLength];
f.InputStream.Read(buf, 0, f.ContentLength);
String content = String.Empty;
for (int i = 0; i < buf.Length; ++i)
{
if (buf[i] == 0)
break;
content += ((char)buf[i]).ToString();
}
byte[] rez = Convert.FromBase64String(content);
string fileName = GetChunkFileName(f.FileName, number);
FileStream fs = new FileStream(fileName, FileMode.CreateNew);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(rez, 0, rez.Length);
bw.Flush();
bw.Close();
fs.Close();
}
private static bool AreAllChunksUploaded(string fileName, int count)
{
for (int i = 1; i <= count; ++i)
{
if (!File.Exists(GetChunkFileName(fileName, i)))
{
return false;
}
}
return true;
}
private static void MergeChunks(string fileName)
{
for (int i = 1; i <= imageInfo.ChunksCount; ++i)
{
FileStream fs = new FileStream(GetChunkFileName(fileName, i), FileMode.Open);
BinaryReader br = new BinaryReader(fs);
byte[] buf = br.ReadBytes(2000);
br.Close();
fs.Close();
fs = new FileStream(GetFileName(fileName), FileMode.Append);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(buf);
bw.Close();
fs.Close();
}
}
#region IHttpHandler members
public void ProcessRequest(HttpContext context)
{
try
{
int number = Convert.ToInt32(context.Request["number"]);
int count = Convert.ToInt32(context.Request["count"]);
string sessionId = context.Request["sessionId"];
HttpPostedFile f = (HttpPostedFile)context.Request.Files[0];
SaveChunkToFile(number, f);
if (AreAllChunksUploaded(f.FileName, count))
{
MergeChunks(f.FileName);
}
context.Response.Write("Upload OK");
}
catch (Exception e)
{
context.Response.Write("Upload failed:\n" + e.Message);
}
}
public bool IsReusable
{
get
{
return false;
}
}
#endregion
}
}
В общем, как-то так.
3 комментария:
Дарья, у мидлета нет ограничения на передачу данных. Это ограничение скорее самого сервера или прокси.
Хмм. Я читала, что это ограничение неподписанных мидлетов. Подписать мидлет мне так и не удалось, так что точно проверить не могу. Но фотографии больше 2 кб точно не передавались. Даже с эмулятора.
Дело в том, что в J2ME реализация HTTP протокола такова, что размер буфера отправки данных ограничен 2 kb (в новых моделях это ограничение убрали). В случае если мидлет пытается послать более 2 kb, то J2ME API автоматически переключается в режим отправки порциями (Transfer-Encoding: chunked) и если сервер умеет такое принимать то всё нормально передастся, иначе ругнётся что-то типа отсутствует поле Content Length или ещё что-нибудь. Кстати к примеру ngnix не умеет принимать порциями.
Отправить комментарий