Cerberus and CerbuinoBee with ENC28 hang on second HTTP post

I have both a Cerberus and CerbuinoBee, each with a ENC28 ethernet module as well as light and temp/humidity sensors. They are both running the same program with a timer that ticks every 10 seconds. Most of the ticks they check and record the sensor data. Every 6th tick, they HTTP post the data (in plain text). The size of the each post (the body text) is about 800 bytes.

The behavior I’m seeing reproduces the same on both of these systems: the first post goes out fine. The second post always hangs in Stream.Write (writing to the request stream). If i wait long enough (a few minutes) I see this exception in the debug output:

A first chance exception of type ‘System.Net.Sockets.SocketException’ occurred in Microsoft.SPOT.Net.dll
Exception performing Timer operation

… and then the program continues. After this I usually see a few more posts go through and then it hangs again.

Does anyone have any clue what is happening here and how to work around it?

Also, the firmware is the same on both of these systems:

HalSystemInfo.halVersion: 4.2.0.0
HalSystemInfo.halVendorInfo: Copyright Oberon microsystems, Inc.
HalSystemInfo.oemCode: 255
HalSystemInfo.modelCode: 0
HalSystemInfo.skuCode: 65535
HalSystemInfo.moduleSerialNumber: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
HalSystemInfo.systemSerialNumber: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
ClrInfo.clrVersion: 4.2.0.0
ClrInfo.clrVendorInfo: Copyright Oberon microsystems, Inc.
ClrInfo.targetFrameworkVersion: 4.2.0.0
SolutionReleaseInfo.solutionVersion: 4.2.3.1
SolutionReleaseInfo.solutionVendorInfo: Copyright © GHI Electronics, LLC
SoftwareVersion.BuildDate: Aug 28 2012
SoftwareVersion.CompilerVersion: 410462
FloatingPoint: True
SourceLevelDebugging: True
ThreadCreateEx: True
LCD.Width: 0
LCD.Height: 0
LCD.BitsPerPixel: 0
AppDomains: True
ExceptionFilters: True
IncrementalDeployment: True
SoftReboot: True
Profiling: False
ProfilingAllocations: False
ProfilingCalls: False
IsUnknown: False

Hi FNord, and welcome to the forums.

Given that this is almost certainly your code at fault, you’re going to need to show us something that shows this, ie as small an example program as possible (if you’re pushing data to COSM or another online DB, take out your private keys before posting this)

Seriously? I would not assume that at all… It could easily be an issue with the LWIP stack. It has been so poorly implemented in .NETMF that I prefer not to use it at all. The first thing I would do is capture a wire shark trace and take a look at what is actually getting sent over the wire.

Maybe a harsh reaction first up, but I think there’s enough collective evidence to believe that in general, the networking isn’t overly unreliable (your experience may have a different benchmark for “reliable” than mine does, but a POST every minute doesn’t sound onerous). It may simply be that the handling of error cases needs to be considered and knowing what is caught and what is not is critical. More debugging information would be awesome too, but seeing code will help us point out where to put that debug.

I was able to cut out all of the sensor stuff and it still reproduces the same way (first post succeeds, second one hangs).

I have posted the test case here: http://www.tinyclr.com/codeshare/entry/579

In the test code, I’m posting to my own server (implemented in ruby/sinatra). I have been successfully posting to the same server from a NetduinoPlus with the latest 4.2 firmware. To run this test, you will need to change the value of string NapkinServerUri to somewhere you can post to, and potentially change the way the NetworkCredential is created for your case also.

It’d be worth showing us the debug output you get between the working (pass 1) case and the not-working (pass 2) case, seeing how you have debug statements in place. Where does your app pause and then report the exception?

(BTW, codeshare isn’t always a good place to put this kind of thing; agreed your project needs more than just a block of code in a post without work, perhaps skydrive/dropbox or something might have been a better target?)

Below is my debug output, ending with the hang on the second post:


Found debugger!

Create TS.

Loading start at 806fd3c, end 8098958

Assembly: mscorlib (4.2.0.0) Assembly: Microsoft.SPOT.Native (4.2.0.0) Assembly: Microsoft.SPOT.Hardware (4.2.0.0)
Assembly: Microsoft.SPOT.Graphics (4.2.0.0) Assembly: Microsoft.SPOT.TinyCore (4.2.0.0)
Assembly: Microsoft.SPOT.Hardware.SerialPort (4.2.0.0) Assembly: Microsoft.SPOT.IO (4.2.0.0)
Assembly: System.IO (4.2.0.0) Assembly: Microsoft.SPOT.Hardware.OneWire (4.2.0.0)
Assembly: Microsoft.SPOT.Hardware.Usb (4.2.0.0) Assembly: Microsoft.SPOT.Hardware.PWM (4.2.0.1)
Assembly: Microsoft.SPOT.Net (4.2.0.0) Assembly: System (4.2.0.0) Loading Deployment Assemblies.

Attaching deployed file.

Assembly: GHIElectronics.Gadgeteer.FEZCerbuinoBee (1.0.6.0) Attaching deployed file.

Assembly: GHI.OSHW.Hardware (4.2.3.0) Attaching deployed file.

Assembly: Gadgeteer (2.42.0.0) Attaching deployed file.

Assembly: System.Http (4.2.0.0) Attaching deployed file.

Assembly: Microsoft.SPOT.Net.Security (4.2.0.0) Attaching deployed file.

Assembly: Microsoft.SPOT.Touch (4.2.0.0) Attaching deployed file.

Assembly: GadgeteerApp1 (1.0.0.0) Attaching deployed file.

Assembly: System.Net.Security (4.2.0.0) Resolving.

The debugging target runtime is loading the application assemblies and starting execution.
Ready.

‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\mscorlib.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Native.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Hardware.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Graphics.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.TinyCore.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Hardware.SerialPort.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.IO.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\System.IO.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Hardware.OneWire.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Hardware.Usb.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Hardware.PWM.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Net.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\System.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Gadgeteer\Core\Assemblies.NET Micro Framework 4.2\le\Gadgeteer.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\GHI Electronics\GHI OSHW NETMF v4.2 SDK\Assemblies\le\GHI.OSHW.Hardware.dll’
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\GHI Electronics\GHI .NET Gadgeteer SDK\Mainboards\FEZCerbuinoBee\NETMF 4.2\le\GHIElectronics.Gadgeteer.FEZCerbuinoBee.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Net.Security.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\System.Net.Security.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\System.Http.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Touch.dll’, Symbols loaded.
‘Microsoft.SPOT.Debugger.CorDebug.dll’ (Managed): Loaded ‘C:\Users\user\Documents\Visual Studio 2010\Projects\TestCases\GadgeteerApp1\GadgeteerApp1\bin\Debug\le\GadgeteerApp1.exe’, Symbols loaded.
The thread ‘’ (0x2) has exited with code 0 (0x0).
Warning: socket 3 is not compliant with Gadgeteer : Socket of type O must support analog output functionality
Using mainboard GHI Electronics FEZCerbuinoBee version 1.2
Program Started
The thread ‘’ (0x3) has exited with code 0 (0x0).
YO 0
Status for cycle: 1
memory_samples=1
memory_average=54300
memory_high=54300
memory_low=54300

YO 0
Status for cycle: 2
memory_samples=2
memory_average=55422
memory_high=56544
memory_low=54300

YO 0
Status for cycle: 3
memory_samples=3
memory_average=55796
memory_high=56544
memory_low=54300

YO 0
Status for cycle: 4
memory_samples=4
memory_average=55983
memory_high=56544
memory_low=54300

YO 0
Status for cycle: 5
memory_samples=5
memory_average=56095
memory_high=56544
memory_low=54300

YO 0
YO 1
YO 2
YO 3
YO 3.1
YO 3.2
YO 3.3
YO 3.3.1
YO 3.3.2… buffer_len: 109
YO 3.4
YO 3.5
YO 4
YO 5
Status for cycle: 6
memory_samples=1
memory_average=50844
memory_high=50844
memory_low=50844

The thread ‘’ (0x5) has exited with code 0 (0x0).
YO 0
Status for cycle: 7
memory_samples=2
memory_average=52980
memory_high=55116
memory_low=50844

YO 0
Status for cycle: 8
memory_samples=3
memory_average=53708
memory_high=55164
memory_low=50844

YO 0
Status for cycle: 9
memory_samples=4
memory_average=54072
memory_high=55164
memory_low=50844

YO 0
Status for cycle: 10
memory_samples=5
memory_average=54290
memory_high=55164
memory_low=50844

YO 0
Status for cycle: 11
memory_samples=6
memory_average=54436
memory_high=55164
memory_low=50844

YO 0
YO 1
YO 2
YO 3
YO 3.1
YO 3.2
YO 3.3
YO 3.3.1
YO 3.3.2… buffer_len: 110

You are not telling the stream that it is closing, and attempting to create a new stream for the same uri. This may be causing the server to have an open socket that can not be re-connected to until the server’s socket communication timeout has been reached. This is no guarantee, but this would be the first place I look. Also, what application is running on the server side? If it is a custom server application, are you responding with the appropriate header?

I’ve created a much simpler test case for this:

step 1: create a new Gadgeteer Application (4.2) project
step 2: replace the Program.cs contents with the code below
step 3: build/deploy/debug and note the hang on the second post as shown in debug output below
step 4: comment out call to GetResponseText, build/deploy/debug and note that the hang is avoided

So as step 4 implies, I’ve narrowed this down to whether or not I read the response from the post. My server is returning the message “OK” when it is posted to. As you can see from the debug output, the response contentLength is 2 and on the “CHECK 3” line, that ‘OK’ is the response from the server.


using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Text;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Touch;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;

namespace GadgeteerApp1
{
    public partial class Program
    {
        // URI which will accept plain text post and return "OK"
        public readonly string PostUri = "http://192.168.2.50:4567/chatter";

        // see NetworkCredential constructor below
        public readonly string DeviceId = "test123";
        private NetworkCredential _credential;

        void ProgramStarted()
        {
            Debug.Print("Program Started");
            _credential = new NetworkCredential(DeviceId, DeviceId);

            GT.Timer timer = new GT.Timer(_cycleDelayMilliseconds);
            timer.Tick += new GT.Timer.TickEventHandler(timer_Tick);
            timer.Start();
        }

        private int _cycleCount = 0;
        private readonly int _postCycle = 6;
        private readonly int _cycleDelayMilliseconds = 10 * 1000;

        void timer_Tick(GT.Timer timer)
        {
            Debug.Print("CHECK 0, cycle: " + _cycleCount);

            _cycleCount++;
            if (_cycleCount % _postCycle == 0)
            {
                Debug.Print("CHECK 1");
                string chatterRequestText = "Hello from " + DeviceId + " on cycle " + _cycleCount;

                Debug.Print("CHECK 2");
                string chatterResponseText = DoHttpMethod("POST", PostUri, _credential, chatterRequestText);
                Debug.Print("CHECK 3: " + chatterResponseText);
            }
        }

        public static string DoHttpMethod(string method, string uri, NetworkCredential credential, string requestText)
        {
            string responseText = null;

            Debug.Print("CHECK 2.1");
            using (HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri))
            {
                request.Method = method;
                request.Credentials = credential;

                Debug.Print("CHECK 2.2");

                if (requestText != null)
                {
                    Debug.Print("CHECK 2.3");
                    byte[] buffer = Encoding.UTF8.GetBytes(requestText);
                    request.ContentLength = buffer.Length;
                    request.ContentType = "text/plain";

                    Debug.Print("CHECK 2.3.1");
                    Stream stream = request.GetRequestStream();
                    Debug.Print("CHECK 2.3.2... buffer_len: " + buffer.Length);
                    Debug.GC(true);
                    stream.Write(buffer, 0, buffer.Length);
                }

                Debug.Print("CHECK 2.4 - GetResponseText?");
                // NOTE: comment out line below to avoid hang
                responseText = GetResponseText(request);
            }

            Debug.Print("CHECK 2.5");

            return responseText;
        }

        public static string GetResponseText(HttpWebRequest request)
        {
            string responseText = "";

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                int contentLength = (int)response.ContentLength;
                Debug.Print("CHECK Response.1, contentLength: " + contentLength);
                byte[] buffer = new byte[contentLength];
                Stream stream = response.GetResponseStream();
                int i = 0;
                while (i < contentLength)
                {
                    int readCount = stream.Read(buffer, i, contentLength - i);
                    i += readCount;
                }

                Debug.Print("CHECK Response.2");

                char[] responseChars = Encoding.UTF8.GetChars(buffer);
                responseText = new string(responseChars);
            }

            return responseText;
        }
    }
}

Debug Output - with GetResponseText call enabled

@ fnord - Then this sounds like an issue with the headers. I’m not entirely sure how the HttpWebRequest class works as I usually implement my own HTTP Server/Client over using what is provided however it is most likely looking for an appropriate response in accordance with RFC-2616.

HTTP Header Response Okay

string header = "HTTP/1.1 200 OK" + CRLF
                            + "Server: SimpleHttp (NETMF) (MicroFramework)" + CRLF
                            + "Accept-Ranges:  none" + CRLF
                            + "Cache-Control: no-cache" + CRLF
                            + "Content-Length: " + document.Length.ToString() + CRLF
                            + "Connection: close" + CRLF
                            + "Content-Type: " + content_type + CRLF + CRLF;

HTTP Header Response to bad request

string header = "HTTP/1.1 404 Not Found" + CRLF
                        + "Server: SimpleHttp (NETMF) (MicroFramework)" + CRLF
                        + "Cache-Control: no-cache" + CRLF
                        + "Connection: close" + CRLF + CRLF;

Responding with only simple messages is okay if you create a class that uses raw socket communication, however the HTTP Protocol [em]expects[/em] messages to be formatted such as the above example.

Hi James,

I tried adding stream.close() calls, but this didn’t make a difference. For some reason I was assuming the idiom of “using (HttpWebRequest …) { … }” would handle the closing of the streams. Is that not how it works?

I’ll try to play around with the headers and see what I can figure out…

Has anyone else tried to run this code (posting to any server that will take the post a return a simple response) and seen it either succeed or hang?

@ fnord - Yes, using the “using” scope in that manor is technically appropriate, however, it is always best to call the dispose method yourself instead of assuming the compiler or run-time to do generic work such as that [em]“Just to be sure”[/em].

I’ve revised the test case (see below) to look at status and headers. Note that when I am getting contentLength==2, this must only be for the response body. There is a lot of info coming over in the headers (see my output below also).

Test Case


using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Text;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Touch;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;

namespace GadgeteerApp1
{
    public partial class Program
    {
        // URI which will accept plain text post and return "OK"
        public readonly string PostUri = "http://192.168.2.50:4567/chatter";

        // see NetworkCredential constructor below
        public readonly string DeviceId = "test123";
        private NetworkCredential _credential;

        void ProgramStarted()
        {
            Debug.Print("Program Started");
            _credential = new NetworkCredential(DeviceId, DeviceId);

            GT.Timer timer = new GT.Timer(_cycleDelayMilliseconds);
            timer.Tick += new GT.Timer.TickEventHandler(timer_Tick);
            timer.Start();
        }

        private int _cycleCount = 0;
        private readonly int _postCycle = 6;
        private readonly int _cycleDelayMilliseconds = 10 * 1000;

        void timer_Tick(GT.Timer timer)
        {
            Debug.Print("CHECK 0, cycle: " + _cycleCount);

            _cycleCount++;
            if (_cycleCount % _postCycle == 0)
            {
                Debug.Print("CHECK 1");
                string chatterRequestText = "Hello from " + DeviceId + " on cycle " + _cycleCount;

                Debug.Print("CHECK 2");
                string chatterResponseText = DoHttpMethod("POST", PostUri, _credential, chatterRequestText);
                Debug.Print("CHECK 3: " + chatterResponseText);
            }
        }

        public static string DoHttpMethod(string method, string uri, NetworkCredential credential, string requestText)
        {
            string responseText = null;

            Debug.Print("CHECK 2.1");
            using (HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri))
            {
                request.Method = method;
                request.Credentials = credential;

                Debug.Print("CHECK 2.2");

                if (requestText != null)
                {
                    Debug.Print("CHECK 2.3");
                    byte[] buffer = Encoding.UTF8.GetBytes(requestText);
                    request.ContentLength = buffer.Length;
                    request.ContentType = "text/plain";

                    Debug.Print("CHECK 2.3.1");
                    Stream stream = request.GetRequestStream();
                    Debug.Print("CHECK 2.3.2... buffer_len: " + buffer.Length);
                    Debug.GC(true);
                    stream.Write(buffer, 0, buffer.Length);
                    stream.Close();
                }

                Debug.Print("CHECK 2.4 - GetResponseText?");
                // NOTE: comment out line below to avoid hang
                responseText = GetResponseText(request);
            }

            Debug.Print("CHECK 2.5");

            return responseText;
        }

        public static string GetResponseText(HttpWebRequest request)
        {
            string responseText = "";

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                Debug.Print("CHECK Response.statusCode: " + response.StatusCode);
                Debug.Print("CHECK Response.statusDesc: " + response.StatusDescription);

                WebHeaderCollection headers = response.Headers;
                foreach (string headerKey in headers.AllKeys)
                {
                    String headerVal = headers[headerKey];
                    Debug.Print("CHECK Response.header: " + headerKey + " = " + headerVal);
                }

                int contentLength = (int)response.ContentLength;
                Debug.Print("CHECK Response.1, contentLength: " + contentLength);
                byte[] buffer = new byte[contentLength];
                Stream stream = response.GetResponseStream();
                int i = 0;
                while (i < contentLength)
                {
                    int readCount = stream.Read(buffer, i, contentLength - i);
                    i += readCount;
                }
                stream.Close();

                Debug.Print("CHECK Response.2");

                char[] responseChars = Encoding.UTF8.GetChars(buffer);
                responseText = new string(responseChars);
            }

            return responseText;
        }
    }
}

Output

Just to make sure, use a thread instead of a timer tick. I often faced problems with timer.

@ fnord - I apologize if this is the appropriate answer (I should have seen this before), however, you also may be trying to get a response that simply has not yet arrived. You could try to place this code above your call to GetResponseText():


 DateTime _TimeOutDate = DateTime.Now.AddMilliseconds(30000.00); //30 Second timeout
            while (!request.HaveResponse)
            {
                //Did we time-out?
                if (_TimeOutDate >= DateTime.Now)
                    //Throw desired exception

                Thread.Sleep(1);
            }

You could leave out the calls to the DateTime object as this is only for convenient debugging.

I played around with HaveResponse and got strange results: I always see it return false. I’m attaching a revised test case and output. I’m polling HaveResponse for 6 seconds after the post (3 different places, 2 seconds polling at each) and I never see it return true, though I get response data from the first post before the hang. (Side note: I always see HaveResponse return false on the NetduinoPlus also, though I’m not seeing the POST hang there.)

At this point, I think I can workaround this issue for me by simply ignoring the post response. I was initially interested in seeing data returning from the server, but I don’t actually need it for my application. On the other hand, it seems like there are some bugs under the hood in this area. How does one debug into the HTTP code? I’m also wondering if I will see this if I start doing GET requests (so far I have just been doing POSTs from these ‘Cerb’ boards)

Test Case


using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Text;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Touch;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;

namespace GadgeteerApp1
{
    public partial class Program
    {
        // URI which will accept plain text post and return "OK"
        public readonly string PostUri = "http://192.168.2.50:4567/chatter";

        // see NetworkCredential constructor below
        public readonly string DeviceId = "test123";
        private NetworkCredential _credential;

        void ProgramStarted()
        {
            Debug.Print("Program Started");
            _credential = new NetworkCredential(DeviceId, DeviceId);

            GT.Timer timer = new GT.Timer(_cycleDelayMilliseconds);
            timer.Tick += new GT.Timer.TickEventHandler(timer_Tick);
            timer.Start();
        }

        private int _cycleCount = 0;
        private readonly int _postCycle = 6;
        private readonly int _cycleDelayMilliseconds = 10 * 1000;

        void timer_Tick(GT.Timer timer)
        {
            Debug.Print("CHECK 0, cycle: " + _cycleCount);

            _cycleCount++;
            if (_cycleCount % _postCycle == 0)
            {
                Debug.Print("CHECK 1");
                string chatterRequestText = "Hello from " + DeviceId + " on cycle " + _cycleCount;

                Debug.Print("CHECK 2");
                string chatterResponseText = DoHttpMethod("POST", PostUri, _credential, chatterRequestText);
                Debug.Print("CHECK 3: " + chatterResponseText);
            }
        }

        public static string DoHttpMethod(string method, string uri, NetworkCredential credential, string requestText)
        {
            string responseText = null;

            Debug.Print("CHECK 2.1");
            using (HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri))
            {
                request.Method = method;
                request.Credentials = credential;

                Debug.Print("CHECK 2.2");

                if (requestText != null)
                {
                    Debug.Print("CHECK 2.3");
                    byte[] buffer = Encoding.UTF8.GetBytes(requestText);
                    request.ContentLength = buffer.Length;
                    request.ContentType = "text/plain";

                    Debug.Print("CHECK 2.3.1");
                    Stream stream = request.GetRequestStream();
                    Debug.Print("CHECK 2.3.2... buffer_len: " + buffer.Length);
                    Debug.GC(true);
                    stream.Write(buffer, 0, buffer.Length);
                    stream.Close();
                }

                Debug.Print("CHECK 2.4 - GetResponseText?");
                PollHaveResponse(request);
                // NOTE: comment out line below to avoid hang
                responseText = GetResponseText(request);
            }

            Debug.Print("CHECK 2.5");

            return responseText;
        }

        public static string GetResponseText(HttpWebRequest request)
        {
            string responseText = "";

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                PollHaveResponse(request);

                Debug.Print("CHECK Response.statusCode: " + response.StatusCode);
                Debug.Print("CHECK Response.statusDesc: " + response.StatusDescription);

                WebHeaderCollection headers = response.Headers;
                foreach (string headerKey in headers.AllKeys)
                {
                    String headerVal = headers[headerKey];
                    Debug.Print("CHECK Response.header: " + headerKey + " = " + headerVal);
                }

                int contentLength = (int)response.ContentLength;
                Debug.Print("CHECK Response.1, contentLength: " + contentLength);
                byte[] buffer = new byte[contentLength];
                Stream stream = response.GetResponseStream();
                int i = 0;
                while (i < contentLength)
                {
                    int readCount = stream.Read(buffer, i, contentLength - i);
                    i += readCount;
                }
                stream.Close();

                Debug.Print("CHECK Response.2");

                char[] responseChars = Encoding.UTF8.GetChars(buffer);
                responseText = new string(responseChars);

                PollHaveResponse(request);
            }

            return responseText;
        }

        private static void PollHaveResponse(HttpWebRequest request, int timeoutMilliseconds = 2000)
        {
            DateTime start = DateTime.Now;
            DateTime timeout = start.AddMilliseconds(timeoutMilliseconds);
            while (!request.HaveResponse && (DateTime.Now < timeout))
            {
                Thread.Sleep(10);
            }
            DateTime finish = DateTime.Now;
            TimeSpan consumed = finish.Subtract(start);
            Debug.Print("CHECK HaveResponse: " + request.HaveResponse + " after: " + consumed);
        }
    }
}

Output

you don’t need to show us all the startup junk before the app actually starts… save the bandwidth ! :wink:

@ fnord - With the way you are polling the HaveRequest, it will return the current state of the response object when passing it, because you are passing a copy of the response object, which may have it’s own stream, thus the response would never reach the object in PollHaveResponse. If you want to poll the actual object in memory, you will need to pass the object as an out.


private static void PollHaveResponse(out HttpWebRequest request, int timeoutMilliseconds = 2000)
        {
            ...
        }

And as well when you call it:


PollHaveResponse(out request)

I will also look further into the hangup in POSTing again to see if I can reproduce your error