Does Ocelot supports secure Websockets (wss) - .net-6.0

When I tried using Ocelot as a WebSocket proxy, I could not get it working for wss. I was able to see it working for ws.
When we are trying to proxy for wss getting decrypt operation failed while reading the bytes at the server side socket. With plan ws I am able to get this working.
Ocelot config as follows, where wss proxying is specified:
{
"Routes": [
{
"DownstreamPathTemplate": "/ws",
"UpstreamPathTemplate": "/{anything}",
"DownstreamScheme": "wss",
"DownstreamHostAndPorts": [
{
"Host": "127.0.0.1",
"Port": 8080
}
]
}
]
}
Websocket Server code which listens on port 8080:
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
class Server
{
public static void Main()
{
string ip = "127.0.0.1";
int port = 8080;
var server = new TcpListener(IPAddress.Parse(ip), port);
server.Start();
Console.WriteLine("Server has started on {0}:{1}, Waiting for a connection...", ip, port);
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("A client connected.");
byte[] pfxData = File.ReadAllBytes(#"C:\Users\e409316\Desktop\test.pfx");
var cert = new X509Certificate2(pfxData, "Password1", X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.Exportable);
Stream sourceTcpStream = new SslStream(client.GetStream(), false);
(sourceTcpStream as SslStream).AuthenticateAsServer(
cert,
false,
SslProtocols.Tls12, true);
Stream stream = sourceTcpStream;//client.GetStream();
//Stream stream = client.GetStream();
// enter to an infinite cycle to be able to handle every change in stream
while (true)
{
//while (!stream.DataAvailable) ;
while (client.Available < 3) ; // match against "get"
byte[] bytes = new byte[client.Available];
stream.Read(bytes, 0, client.Available);
string s = Encoding.UTF8.GetString(bytes);
if (Regex.IsMatch(s, "^GET", RegexOptions.IgnoreCase))
{
Console.WriteLine("=====Handshaking from client=====\n{0}", s);
// 1. Obtain the value of the "Sec-WebSocket-Key" request header without any leading or trailing whitespace
// 2. Concatenate it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (a special GUID specified by RFC 6455)
// 3. Compute SHA-1 and Base64 hash of the new value
// 4. Write the hash back as the value of "Sec-WebSocket-Accept" response header in an HTTP response
string swk = Regex.Match(s, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim();
string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
byte[] swkaSha1 = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swka));
string swkaSha1Base64 = Convert.ToBase64String(swkaSha1);
// HTTP/1.1 defines the sequence CR LF as the end-of-line marker
byte[] response = Encoding.UTF8.GetBytes(
"HTTP/1.1 101 Switching Protocols\r\n" +
"Connection: Upgrade\r\n" +
"Upgrade: websocket\r\n" +
"Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n");
stream.Write(response, 0, response.Length);
}
else
{
bool fin = (bytes[0] & 0b10000000) != 0,
mask = (bytes[1] & 0b10000000) != 0; // must be true, "All messages from the client to the server have this bit set"
int opcode = bytes[0] & 0b00001111, // expecting 1 - text message
msglen = bytes[1] - 128, // & 0111 1111
offset = 2;
if (msglen == 126)
{
// was ToUInt16(bytes, offset) but the result is incorrect
msglen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);
offset = 4;
}
else if (msglen == 127)
{
Console.WriteLine("TODO: msglen == 127, needs qword to store msglen");
// i don't really know the byte order, please edit this
// msglen = BitConverter.ToUInt64(new byte[] { bytes[5], bytes[4], bytes[3], bytes[2], bytes[9], bytes[8], bytes[7], bytes[6] }, 0);
// offset = 10;
}
if (msglen == 0)
Console.WriteLine("msglen == 0");
else if (mask)
{
byte[] decoded = new byte[msglen];
byte[] masks = new byte[4] { bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3] };
offset += 4;
for (int i = 0; i < msglen; ++i)
decoded[i] = (byte)(bytes[offset + i] ^ masks[i % 4]);
string text = Encoding.UTF8.GetString(decoded);
Console.WriteLine("{0}", text);
}
else
Console.WriteLine("mask bit not set");
Console.WriteLine();
}
}
}
}
Websocket Client code which tries to connect to ocelot endpoint (upstream endpoint on port 5000):
ClientWebSocket client = new ClientWebSocket();
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
client.ConnectAsync(new Uri("wss://127.0.0.1:5000/"), CancellationToken.None).Wait();
var buffer = new byte[]{1,2,3};
client.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true,CancellationToken.None);
Error: The decryption operation failed

Found it to be a certificate validity issue. Solved it by using a trusted certificate.

Related

ESP32 send large file (http post) to server from LittleFS

I'm trying to post a (large) json file that's stored in the SPIFFS (LittleFS- of an arduino.
I first of all, fetch the JSON from a local IP address, which can change or is dynamic. Then, that json file gets stored in the internal flash of my ESP32 and sent over to our servers.
The goal is to have an ESP32 act as a bridge for local addresses.
For example:
Locally, there is a server running, that server is never accessible from outside of a network, but it is from within. We place an ESP32 in that network, to be able to read out data from the internal device and pass them through to our servers, for further processing.
The current code I have is the following;
HTTPClient http;
if (LittleFS.exists("/downloadedFile.json")) {
LittleFS.remove("/downloadedFile.json");
}
File f = LittleFS.open("/downloadedFile.json", "w", true);
if (f) {
String url;
// Build the URL...
if (username != "" && password != "") {
url = "http://" + username + ":" + password + "#" + ip + ":" + String(port) + "/data/file.json";
} else {
url = "http://" + ip + ":" + String(port) + "/data/file.json";
}
http.begin(url);
int httpCode = http.GET();
if (httpCode > 0) {
if (httpCode == HTTP_CODE_OK) {
http.writeToStream(&f);
}
} else {
// failed.
}
f.close();
} else {
// Unable to open file
}
http.end();
if (LittleFS.exists("/downloadedFile.json")) {
File f = LittleFS.open("/downloadedFile.json", "r");
if (f) {
WiFiClienSecure client;
client.setInsecure(); // test
if (!client.connect("my.somain.com", 443)) {
// failed to connect. (never gets in here)
return;
}
client.println("POST /path/to/my/endpoint HTTP/1.1");
client.println("Host: my.somain.com");
client.println("Content-Type: application/json");
client.println("Connection: keep-alive");
client.println("Content-Length: " + String(f.size()));
client.println();
// Build the body
int len = 0;
int total = 0;
uint8_t buf[1025] = {};
size_t _len = 256;
do {
len = f.read(buf, _len);
client.write(buf, len);
total += len;
} while (len > 0);
client.println(); // end line
client.stop();
f.close();
}
}
Downloading the file always works, except for sometimes random crashing the whole system. But that's the least of my concerns.
Locally I got it working to download and re-upload the file. Struggle is when I try to achieve the same result on the server (which uses https)
Only difference is this;
Server
Local
WiFiClienSecure client;
WiFiClient client;
client.setInsecure();
client.connect("my.somain.com", 443)
client.connect("IP", 80)
The main problem is that it doesn't work on the server and that it works really unstable. the file size (when I do f.size()) is 310831.
Any help and thoughts on getting this stable is appriciated!

Unable to connect https protocol with ESP8266 using WifiClientSecure

I'm trying to get ESP8266 position by unwiredlabs. I followed with this introduction.
This is my arduino code:
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>
#include "ESP8266WiFi.h"
char myssid[] = "Your wifi/hotspot name";
char mypass[] = "Your password";
const char* Host = "www.unwiredlabs.com";
String endpoint = "/v2/process.php";
String token = "d99cccda52ec0b";
String jsonString = "{\n";
double latitude = 0.0;
double longitude = 0.0;
double accuracy = 0.0;
void setup(){
Serial.begin(115200);
// Set WiFi to station mode and disconnect from an AP if it was previously connected
WiFi.mode(WIFI_STA);
WiFi.disconnect();
Serial.println("Setup done");
// We start by connecting to a WiFi network
Serial.print("Connecting to ");
Serial.println(myssid);
WiFi.begin(myssid, mypass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(".");
}
void loop() {
char bssid[6];
DynamicJsonBuffer jsonBuffer;
// WiFi.scanNetworks will return the number of networks found
int n = WiFi.scanNetworks();
Serial.println("scan done");
if (n == 0 ) {
Serial.println("No networks available");
} else {
Serial.print(n);
Serial.println(" networks found");
}
// now build the jsonString...
jsonString = "{\n";
jsonString += "\"token\" : \"";
jsonString += token;
jsonString += "\",\n";
jsonString += "\"id\" : \"saikirandevice01\",\n";
jsonString += "\"wifi\": [\n";
for (int j = 0; j < n; ++j) {
jsonString += "{\n";
jsonString += "\"bssid\" : \"";
jsonString += (WiFi.BSSIDstr(j));
jsonString += "\",\n";
jsonString += "\"signal\": ";
jsonString += WiFi.RSSI(j);
jsonString += "\n";
if (j < n - 1) {
jsonString += "},\n";
} else {
jsonString += "}\n";
}
}
jsonString += ("]\n");
jsonString += ("}\n");
Serial.println(jsonString);
WiFiClientSecure client;
//Connect to the client and make the api call
Serial.println("Requesting URL: https://" + (String)Host + endpoint);
if (client.connect(Host, 443)) {
Serial.println("Connected");
client.println("POST " + endpoint + " HTTP/1.1");
client.println("Host: " + (String)Host);
client.println("Connection: close");
client.println("Content-Type: application/json");
client.println("User-Agent: Arduino/1.0");
client.print("Content-Length: ");
client.println(jsonString.length());
client.println();
client.print(jsonString);
delay(500);
}
//Read and parse all the lines of the reply from server
while (client.available()) {
String line = client.readStringUntil('\r');
JsonObject& root = jsonBuffer.parseObject(line);
if (root.success()) {
latitude = root["lat"];
longitude = root["lon"];
accuracy = root["accuracy"];
Serial.println();
Serial.print("Latitude = ");
Serial.println(latitude, 6);
Serial.print("Longitude = ");
Serial.println(longitude, 6);
Serial.print("Accuracy = ");
Serial.println(accuracy);
}
}
Serial.println("closing connection");
Serial.println();
client.stop();
delay(5000);
}
When code had been flashed to esp8266, it showed that could not to connect to https://www.instructables.com/v2/process.php.
ESP serial output:
... // some initial setup string
Requesting URL: https://unwiredlabs.com/v2/process.php
// if connected, "connected" was printed here, but not
closing connection
Then, I tried to use url https://unwiredlabs.com/v2/process.php on chrome browser. This is message:
{
status: "error",
message: "Invalid request",
balance: 0,
help: "Check for misplaced commas and use double quotes to encapsulate strings"
}
This proved that this url existed, but when i tried on Postman, it showed:
Then, I turned off SSL certificate verifycation on Postman. It responsed with a 403 Forbidden error.
So i think the reason caused problem is SSL certificate verifycation of WifiClientSecure.
Anyone can help?
SSL - at least the way this code is trying to use it - requires the Fingerprint of the site you're trying to connect to. The code needs to tell its client object the fingerprint of the site before trying to connect to that site.
Step 1: manually retrieve the fingerprint from the site:
I browsed to https://www.unwiredlabs.com in Chrome and copied the site cerficate, then used openSSL in git bash on Windows to extract the fingerprint:
openssl x509 -noout -fingerprint -sha1 -inform pem -in certificate-file.cer > fingerprint.txt
I then edited the resulting fingerprint.txt file, replacing each ':' with a space.
Search the net for details on how to copy the cite certificate using Chrome, or whatever browser you're using.
Step 2: edit the code to add the fingerprint to the Sketch:
I added the constant 'sslFingerprint', and added a call to client.setFingerprint() just before calling client.connect().
I then removed the code unrelated to connecting to the site, creating an example Sketch that illustrates a successful connection to unwiredlabs.com:
#include <ESP8266HTTPClient.h>
#include "ESP8266WiFi.h"
// The SSL Fingerprint of https://www.unwiredlabs.com
// Certificate expires ‎October ‎9, ‎2020
const char *sslFingerprint
= "C3 3E 2F 21 CB 15 4E 02 D5 27 E5 F6 EF FB 31 AE 91 51 A3 5D";
char myssid[] = "yourWiFiSSID";
char mypass[] = "yourWiFiPassword";
const char* Host = "www.unwiredlabs.com";
String endpoint = "/v2/process.php";
void setup() {
Serial.begin(9600);
// Set WiFi to station mode and disconnect from an AP if it was previously connected
WiFi.mode(WIFI_STA);
WiFi.disconnect();
Serial.println("Setup done");
// We start by connecting to a WiFi network
Serial.print("Connecting to ");
Serial.println(myssid);
WiFi.begin(myssid, mypass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(".");
}
void loop() {
WiFiClientSecure client;
//Connect to the client and make the api call
Serial.println("Requesting URL: https://" + (String)Host + endpoint);
client.setFingerprint(sslFingerprint);
if (client.connect(Host, 443)) {
Serial.println("Connected");
}
Serial.println("closing connection");
Serial.println();
client.stop();
delay(5000);
}
When run on a Sparkfun ESP8266 Thing Dev board, the Sketch produces this output:
......
Requesting URL: https://www.unwiredlabs.com/v2/process.php
Connected
closing connection

NetMQ client to client messaging

I'm trying to create an rpc program to communicate hosts located on different networks and chose Router-Dealer configuration of NetMQ provided here: http://netmq.readthedocs.io/en/latest/router-dealer/#router-dealer
But the problem is that router always selects a random dealer when routing a message to backend.
Code which I used :
using (var frontend = new RouterSocket(string.Format("#tcp://{0}:{1}", "127.0.0.1", "5556")))//"#tcp://10.0.2.218:5559"
using (var backend = new DealerSocket(string.Format("#tcp://{0}:{1}", "127.0.0.1", "5557")))//"#tcp://10.0.2.218:5560"
{
// Handler for messages coming in to the frontend
frontend.ReceiveReady += (s, e) =>
{
Console.WriteLine("message arrived on frontEnd");
NetMQMessage msg = e.Socket.ReceiveMultipartMessage();
string clientAddress = msg[0].ConvertToString();
Console.WriteLine("Sending to :" + clientAddress);
//TODO: Make routing here
backend.SendMultipartMessage(msg); // Relay this message to the backend };
// Handler for messages coming in to the backend
backend.ReceiveReady += (s, e) =>
{
Console.WriteLine("message arrived on backend");
var msg = e.Socket.ReceiveMultipartMessage();
frontend.SendMultipartMessage(msg); // Relay this message to the frontend
};
using (var poller = new NetMQPoller { backend, frontend })
{
// Listen out for events on both sockets and raise events when messages come in
poller.Run();
}
}
Code for Client:
using (var client = new RequestSocket(">tcp://" + "127.0.0.1" + ":5556"))
{
var messageBytes = UTF8Encoding.UTF8.GetBytes("Hello");
var messageToServer = new NetMQMessage();
//messageToServer.AppendEmptyFrame();
messageToServer.Append("Server2");
messageToServer.Append(messageBytes);
WriteToConsoleVoid("======================================");
WriteToConsoleVoid(" OUTGOING MESSAGE TO SERVER ");
WriteToConsoleVoid("======================================");
//PrintFrames("Client Sending", messageToServer);
client.SendMultipartMessage(messageToServer);
NetMQMessage serverMessage = client.ReceiveMultipartMessage();
WriteToConsoleVoid("======================================");
WriteToConsoleVoid(" INCOMING MESSAGE FROM SERVER");
WriteToConsoleVoid("======================================");
//PrintFrames("Server receiving", clientMessage);
byte[] rpcByteArray = null;
if (serverMessage.FrameCount == 3)
{
var clientAddress = serverMessage[0];
rpcByteArray = serverMessage[2].ToByteArray();
}
WriteToConsoleVoid("======================================");
Console.ReadLine();
}
Code for Dealer:
using (var server = new ResponseSocket())
{
server.Options.Identity = UTF8Encoding.UTF8.GetBytes(confItem.ResponseServerID);
Console.WriteLine("Server ID:" + confItem.ResponseServerID);
server.Connect(string.Format("tcp://{0}:{1}", "127.0.0.1", "5557"));
using (var poller = new NetMQPoller { server })
{
server.ReceiveReady += (s, a) =>
{
byte[] response = null;
NetMQMessage serverMessage = null;
try
{
serverMessage = a.Socket.ReceiveMultipartMessage();
}
catch (Exception ex)
{
Console.WriteLine("Exception on ReceiveMultipartMessage : " + ex.ToString());
//continue;
}
byte[] eaBody = null;
string clientAddress = "";
if (serverMessage.FrameCount == 2)
{
clientAddress = serverMessage[0].ConvertToString();
Console.WriteLine("ClientAddress:" + clientAddress);
eaBody = serverMessage[1].ToByteArray();
Console.WriteLine("Received message from remote computer: {0} bytes , CurrentID : {1}", eaBody.Length, confItem.ResponseServerID);
}
else
{
Console.WriteLine("Received message from remote computer: CurrentID : {0}", confItem.ResponseServerID);
}
};
poller.Run();
}
}
Is it possible to choose a specific backend on frontend.ReceiveReady?
Thanks!
Your backend should be router as well. You need the worker to register or you need to know all the available workers and their identity. When send on the backend push the worker identity at the beginning of the server.
Take a look at the Majordomo example in the zeromq guide:
http://zguide.zeromq.org/page:all#toc72
http://zguide.zeromq.org/page:all#toc98

Socket Server Example with Swift

I tried to make an example of simple socket server.
Build and run successfully. However it doesn't work well.
Client couldn't connect to this server.
How to solve this problem? I need your help, thanks.
import Foundation
let BUFF_SIZE = 1024
func initStruct<S>() -> S {
let struct_pointer = UnsafePointer<S>.alloc(1)
let struct_memory = struct_pointer.memory
struct_pointer.destroy()
return struct_memory
}
func sockaddr_cast(p: ConstUnsafePointer<sockaddr_in>) -> UnsafePointer<sockaddr> {
return UnsafePointer<sockaddr>(p)
}
func socklen_t_cast(p: UnsafePointer<Int>) -> UnsafePointer<socklen_t> {
return UnsafePointer<socklen_t>(p)
}
var server_socket: Int32
var client_socket: Int32
var server_addr_size: Int
var client_addr_size: Int
var server_addr: sockaddr_in = initStruct()
var client_addr: sockaddr_in = initStruct()
var buff_rcv: Array<CChar> = []
var buff_snd: String
server_socket = socket(PF_INET, SOCK_STREAM, 0);
if server_socket == -1
{
println("[Fail] Create Server Socket")
exit(1)
}
else
{
println("[Success] Created Server Socket")
}
server_addr_size = sizeof(server_addr.dynamicType)
memset(&server_addr, 0, UInt(server_addr_size));
server_addr.sin_family = sa_family_t(AF_INET)
server_addr.sin_port = 4000
server_addr.sin_addr.s_addr = UInt32(0x00000000) // INADDR_ANY = (u_int32_t)0x00000000 ----- <netinet/in.h>
let bind_server = bind(server_socket, sockaddr_cast(&server_addr), socklen_t(server_addr_size))
if bind_server == -1
{
println("[Fail] Bind Port");
exit(1);
}
else
{
println("[Success] Binded Port");
}
if listen(server_socket, 5) == -1
{
println("[Fail] Listen");
exit(1);
}
else
{
println("[Success] Listening : \(server_addr.sin_port) Port ...");
}
var n = 0
while n < 1
{
client_addr_size = sizeof(client_addr.dynamicType)
client_socket = accept(server_socket, sockaddr_cast(&client_addr), socklen_t_cast(&client_addr_size))
if client_socket == -1
{
println("[Fail] Accept Client Connection");
exit(1);
}
else
{
println("[Success] Accepted Client : \(inet_ntoa(client_addr.sin_addr)) : \(client_addr.sin_port)");
}
read(client_socket, &buff_rcv, UInt(BUFF_SIZE))
println("[Success] Received : \(buff_rcv)")
buff_snd = "\(strlen(buff_rcv)) : \(buff_rcv)"
write(client_socket, &buff_snd, strlen(buff_snd) + 1)
close(client_socket)
}
The port number in the socket address must be in big-endian byte order:
server_addr.sin_port = UInt16(4000).bigEndian
So your program actually listens on port 40975 (hex 0xA00F) and not
on port 4000 (hex 0x0FA0).
Another problem is here:
var buff_rcv: Array<CChar> = []
// ...
read(client_socket, &buff_rcv, UInt(BUFF_SIZE))
Your buffer is an empty array, but recv() expects a buffer of size BUFF_SIZE.
The behaviour is undefined. To get a buffer of the required size, use
var buff_rcv = [CChar](count:BUFF_SIZE, repeatedValue:0)
// ...
read(client_socket, &buff_rcv, UInt(buff_rcv.count))
Remark: Here you cast the address of an Int to the address of an socklen_t
and pass that to the accept() function:
client_socket = accept(server_socket, sockaddr_cast(&client_addr), socklen_t_cast(&client_addr_size))
That is not safe. If Int and socklen_t have different sizes then the behaviour
will be undefined. You should declare server_addr_size and client_addr_size
as socklen_t and remove the socklen_t_cast() function:
client_socket = accept(server_socket, sockaddr_cast(&client_addr), &client_addr_size)
As Martin R commented before, the write command shouldn't be using the swift string as that. Something like this will work properly:
write(client_socket, buff_snd.cStringUsingEncoding(NSUTF8StringEncoding)!, countElements(buff_snd) + 1)

Posting file on Background Agent / HttpWebRequest stream buffer keeps growing?

I need to POST a 5MB file from within a ResourceIntensiveTask, where the OS sets a max memory usage of 5MB.
So trying to stream the file directly from storage, but the Stream associated to the HttpWebRequest keeps growing in size. This is the code:
public void writeStream(Stream writer, string filesource, string filename)
{
var store = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication();
var f = store.OpenFile(filesource, FileMode.Open, FileAccess.Read);
store.Dispose();
byte[] buffer = Encoding.UTF8.GetBytes(String.Format(#"Content-Disposition: form-data; name=""file""; filename=""{0}""\n", filename));
writer.Write(buffer, 0, buffer.Length);
buffer = Encoding.UTF8.GetBytes("Content-Type: application/octet-stream\n");
writer.Write(buffer, 0, buffer.Length);
long initialMemory = Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage;
buffer = new byte[2048];
int DataRead = 0;
do
{
DataRead = f.Read(buffer, 0, 2048);
if (DataRead > 0)
{
writer.Write(buffer, 0, DataRead);
Array.Clear(buffer, 0, 2048);
}
} while (DataRead > 0);
double increasedMemory = ((double)Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage - initialMemory) / 1000000;
buffer = Encoding.UTF8.GetBytes("\n--" + boundary + "\n--");
writer.Write(buffer, 0, buffer.Length);
writer.Flush();
}
increasedMemory debug variable is used to get the differential memory before and after the file is read and streamed to the HttpWebRequest, and it gives almost the exact size of the file (5MB) which means the process memory is increasing 5MB.
I am also setting AllowReadStreamBuffering=false to the HttpWebRequest.
How to keep memory low? How to upload large files when memory usage limit is 5MB?
The problem is that without being able to turn off write buffering, the connection to the server is not even made until BeginGetResponse() is called after closing the request stream (verified with WireShark).
The only way I can think of to get around this would be to use sockets directly (although that will be way more complicated if using an SSL connection).
This code works for me and doesn't increase memory usage while sending data to the server. I haven't tested it in a background task but don't see any reason it wouldn't work.
Socket _socket;
const int BUFFERSIZE = 4096;
byte[] writebuffer = new byte[BUFFERSIZE];
string hostName = "www.testdomain.com";
string hostPath = "/test/testupload.aspx";
IsolatedStorageFileStream isoFile;
public void SocketPOST(string hostName, string filesource)
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
if (store.FileExists(filesource))
{
isoFile = store.OpenFile(filesource, FileMode.Open, FileAccess.Read);
}
}
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.SetNetworkRequirement(NetworkSelectionCharacteristics.NonCellular);
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = new DnsEndPoint(hostName, 80);
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(Socket_Completed);
_socket.ConnectAsync(socketEventArg);
}
private void Socket_Completed(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect: // Connected so started sending data, headers first
if (e.ConnectSocket.Connected)
{
StringBuilder sbHeaders = new StringBuilder("POST " + hostPath + " HTTP/1.1\r\n");
sbHeaders.Append("HOST: " + hostName + "\r\n");
sbHeaders.Append("USER-AGENT: MyWP7App/1.0\r\n");
sbHeaders.Append("Content-Type: text/plain; charset=\"utf-8\"\r\n");
sbHeaders.Append("Content-Length: " + isoFile.Length.ToString() + "\r\n\r\n");
byte[] headerBuffer = Encoding.UTF8.GetBytes(sbHeaders.ToString());
e.SetBuffer(headerBuffer, 0, headerBuffer.Length);
if (!e.ConnectSocket.SendAsync(e)) Socket_Completed(e.ConnectSocket, e);
}
break;
case SocketAsyncOperation.Send:
case SocketAsyncOperation.SendTo: // Previous buffer sent so send next one if stream not finished
Array.Clear(writebuffer, 0, BUFFERSIZE);
int DataRead = 0;
DataRead = isoFile.Read(writebuffer, 0, BUFFERSIZE);
if (DataRead > 0)
{
e.SetBuffer(writebuffer, 0, DataRead);
if (!_socket.SendAsync(e)) Socket_Completed(e.ConnectSocket, e);
}
else
{
isoFile.Dispose();
if (!_socket.ReceiveAsync(e)) Socket_Completed(e.ConnectSocket, e);
}
break;
case SocketAsyncOperation.Receive:
case SocketAsyncOperation.ReceiveFrom:
if (e.BytesTransferred > 0)
{
string response = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred).Trim('\0');
// Check response if necessary
e.ConnectSocket.Shutdown(SocketShutdown.Both);
e.ConnectSocket.Dispose();
}
break;
default:
break;
}
}
}
Note: I've left a lot of the error handling out to keep the example short.
SSL Note: Because SSL works at the TCP level and WP7 doesn't currently support SSL sockets (SslStream) you would need to handle the certificate handshake, cipher exchange, etc yourself to set up the SSL connection on the socket and then encrypt everything being sent (and decrypt everything received) with the agreed algorithms. There has been some success using the Bouncy Castle API so that could be possible (see this blog post).
One thing I noticed: you forgot to dispose f!
I personally would use the code like this:
public void writeStream(Stream writer, string filesource, string filename)
{
using (var store = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication())
{
long initialMemory = Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage;
using (var f = store.OpenFile(filesource, FileMode.Open, FileAccess.Read))
{
byte[] buffer = Encoding.UTF8.GetBytes(string.Format(#"Content-Disposition: form-data; name=""file""; filename=""{0}""\n", filename));
writer.Write(buffer, 0, buffer.Length);
buffer = Encoding.UTF8.GetBytes("Content-Type: application/octet-stream\n");
writer.Write(buffer, 0, buffer.Length);
buffer = new byte[2048];
int DataRead = 0;
do
{
DataRead = f.Read(buffer, 0, 2048);
if (DataRead > 0)
{
writer.Write(buffer, 0, DataRead);
}
} while (DataRead > 0);
buffer = Encoding.UTF8.GetBytes("\n--" + boundary + "\n--");
writer.Write(buffer, 0, buffer.Length);
writer.Flush();
}
double increasedMemory = ((double)Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage - initialMemory) / 1000000;
}
}
The boundary var seems to be missing, so a coding error still remains here!

Resources