Content-Length = 0 on Android

Topics: Web Api
Oct 4, 2011 at 9:33 PM

I have a problem I cannot seem to solve. I've got a service running that is working perfectly and consistently when I access it from another .net project. It works on my local system and it works when deployed on an Azure server with https enabled, etc. I'm really happy with it at the moment. Except that when I try to access it from an Android client it does not work. The same code that worked against a WCF REST service from our Android apps does not work against the Web API implementation. I've tried a variety of changes, mostly on the server side to get it to work with no joy. The last change I attempeted was to change the configuration's TransferMode between Streaming and Buffered.

The problem I am seeing on Android is that the Content-Length reported by the HttpClient has a length of 0. While debugging, if I drill down to the Entity/Content object in the response of our call the Status is a 200, as expected, and the content seems to be in the buffer but it is not accessible for some reason and as a result the Content-Length is 0. Looking at the response.entity.wrappedEntity.content.in.bufferlen the value is 178. The data we expect back is 104 bytes long, I'm guessing the extra bytes are part of the protocol. I'm not an expert on the http protocol and I don't want to become one. <shrug> I just need this to work.

Again, the same service called from a .net test project works perfectly...

Any input would be most welcomed.

 

Coordinator
Oct 5, 2011 at 6:13 AM

Could you please try using a tool like Fiddler to verify that the content length is specified correctly on the wire?

Daniel Roth

From: WillTartak [email removed]
Sent: Tuesday, October 04, 2011 1:34 PM
To: Daniel Roth
Subject: Content-Length = 0 on Android [wcf:274762]

From: WillTartak

I have a problem I cannot seem to solve. I've got a service running that is working perfectly and consistently when I access it from another .net project. It works on my local system and it works when deployed on an Azure server with https enabled, etc. I'm really happy with it at the moment. Except that when I try to access it from an Android client it does not work. The same code that worked against a WCF REST service from our Android apps does not work against the Web API implementation. I've tried a variety of changes, mostly on the server side to get it to work with no joy. The last change I attempeted was to change the configuration's TransferMode between Streaming and Buffered.

The problem I am seeing on Android is that the Content-Length reported by the HttpClient has a length of 0. While debugging, if I drill down to the Entity/Content object in the response of our call the Status is a 200, as expected, and the content seems to be in the buffer but it is not accessible for some reason and as a result the Content-Length is 0. Looking at the response.entity.wrappedEntity.content.in.bufferlen the value is 178. The data we expect back is 104 bytes long, I'm guessing the extra bytes are part of the protocol. I'm not an expert on the http protocol and I don't want to become one. <shrug> I just need this to work.

Again, the same service called from a .net test project works perfectly...

Any input would be most welcomed.

Oct 5, 2011 at 7:55 PM

Hi Dan,

Thanks for the input. I tried to run Fiddler on our staging Azure instance to no avail. I've spent 3 hours on that and I need to move on. I wish there were an easier way to figure out what is happening.

Oct 5, 2011 at 9:48 PM

Would it be possible to post the fiddler trace so we can see what is going on?

Thanks,

Henrik

Oct 5, 2011 at 10:20 PM

Hi Henrik,

I can't get a Fiddler to track anything on an Azure instance and running it on my dev box is worthless because Android can't access the dev box server. I wish I could see the Fiddler traces but so far I've had no luck getting it to work in an actual Azure box.

For the moment I'm changing the content loading from using a StringContent class to a SteamContent to see if perhaps that helps. I'll post here once I know more.

Best,

\ ^ / i l l

Oct 6, 2011 at 5:01 AM

I'm still trying to make this work. I have another method that is very similar in functionality, GetArticleList. It is working fine, even against Android. The method I'm having problems with CheckDataLast is a post method whereas the GetArticleList method is a Get method. Is it possible that the problem I'm seeing is related to the method being a post? If yes, how do I get a post message to return the content assigned to it? Both methods are returning an HttpResponseMessage whose content has been set with either StringContent or StreamContent, I've tried both with similar results.

Coordinator
Oct 6, 2011 at 6:22 AM

Hi Will

Can you show us the code for the CheckDataList method?

Thanks

Glenn

Oct 6, 2011 at 5:10 PM

Hi Glen,

Sure, here it is...

[OperationContract,
WebInvoke(UriTemplate = "/{leagueName}/{seasonYearStart}/{teamName}", Method = "POST")]
public HttpResponseMessage CheckDataLast(HttpRequestMessage req, string leagueName, string seasonYearStart, string teamName)
{
	var result = new HttpResponseMessage();
	// This is to force the new Wcf Web Api system to return json, instead of xml, the default
	// it expects the Accept header to tell it this but our first rest releases didn't use the
	// accept headers at all and break without us forcing the issue here.
	String defaultOutputType = "application/json";
	//if (req.Headers.Accept.Count == 0)
	//    HeaderHelper.OverwriteAcceptHeader(req, defaultOutputType);

	HeaderHelper.OverwriteAcceptHeader(req, defaultOutputType);
			
	string content = string.Empty;
	string xmlData = req.Content.ReadAsString();

	if (!SecurityHelper.IsUserAuthenticated(req))
	{
		result.StatusCode = HttpStatusCode.Unauthorized;

	}
	else
	{
		CultureInfo ci = CultureInfo.InvariantCulture;
		string container = string.Empty;
		List<string> toUpdate = new List<string>();
		try
		{
			XmlDocument fromDevice = new XmlDocument();
			fromDevice.LoadXml(xmlData);
			LeagueSeasonRo season;
			if (Seasons.ContainsKey(leagueName))
			{
				season = Seasons[leagueName];

			}
			else
			{
				season = LeagueSeasonRoList.GetByLeagueName(leagueName)[0];
				Seasons.Add(leagueName, season);

			}		// (Seasons.ContainsKey(leagueName))

					// Read the server fileName to an XmlReader
				BlobManager mgr;
				if (season.useLeaguesContainer == "N")
				{
					mgr = CommHelper.GetMakeBlobManager(leagueName);

				}
				else
				{
					mgr = CommHelper.GetMakeBlobManager(CommHelper.EXTRACTION_MANAGER_NAME);

				}		// (season.useLeaguesContainer == "N")

				container = mgr.GetBlobContainer().Uri.ToString().ToLowerInvariant();
				// Now compare the values with the server datalast.xml
				string teamPath = Utility.BuildPath(container, leagueName, seasonYearStart, teamName);
				string serverFile = String.Format(@"{0}/{1}", teamPath, "datalast.xml");
				// Did we cache the file?
			         XmlDocument fromServer = new XmlDocument();
				if (DefaultCacheProvider.Contains(serverFile))
				{
				         fromServer = (XmlDocument)DefaultCacheProvider.Get(serverFile);

				}
				else
				{
					using (MemoryStream readMe = mgr.GetBlob(serverFile))
					{
						readMe.Position = 0;
						fromServer.Load(readMe);

					}		// using (MemoryStream readMe = this.extractionBlobMgr.GetBlob(serverFile))

					// Cache it for later use, this is an optimization
			                  DefaultCacheProvider.Add(serverFile, fromServer);

				}		// (files.ContainsKey(serverFile))

				// Compare each entry 
			         XmlNamespaceManager nsMgr = new XmlNamespaceManager(fromServer.NameTable);
				nsMgr.AddNamespace("dlf", "http://www.teampad.info/2010/v1/datalast");
				XmlElement fsRoot = fromServer.DocumentElement;
				XmlNodeList dataFiles = fsRoot.SelectNodes(@"/dlf:dataLastFiles/dlf:dataFile", nsMgr);
				foreach (XmlNode fsDataFile in dataFiles)
				{
						string fileName = fsDataFile.Attributes["name"].Value;
						string fsToParse = fsDataFile.Attributes["lastDateTime"].Value;
						if (string.IsNullOrWhiteSpace(fsToParse))
							continue;

						DateTimeOffset fsStamp = DateTimeOffset.ParseExact(fsToParse, "u", ci);
						XmlNode fileEl = fromDevice.DocumentElement.SelectSingleNode(
								String.Format(@"/dlf:dataLastFiles/dlf:dataFile[@name='{0}']", fileName), nsMgr);
						string fdToParse = fileEl.Attributes["lastDateTime"].Value;
						if (string.IsNullOrWhiteSpace(fdToParse))
						{
							toUpdate.Add(fileName);

						}
						else
						{
							DateTimeOffset fdStamp = DateTimeOffset.ParseExact(fdToParse, "u", ci);
							if (fsStamp > fdStamp)
							{
								// Keep track of any changes
								toUpdate.Add(fileName);

							}		// (fsStamp > fdStamp)

						}		// (string.IsNullOrWhiteSpace(fdToParse))

					}		// foreach (XmlNode fsDataFile in dataFiles)

					if (toUpdate.Count > 0)
					{
						if (!toUpdate.Contains("datalast.xml"))
							toUpdate.Add("datalast.xml");

						content = JsonConvert.SerializeObject(toUpdate);
						result.Content = new StringContent(content, UTF8Encoding.UTF8, defaultOutputType);
						//byte[] buffer = UTF8Encoding.UTF8.GetBytes(content);
						//MemoryStream ms = new MemoryStream(buffer);
						//result.Content = new StreamContent(ms);

					}		// (toUpdate.Count > 0)

					result.StatusCode = HttpStatusCode.OK;

				}
				catch (Exception e)
				{
					Trace.Write(string.Format("tph.GetFile error {0} for {1}, at {2} with stacktrace {3}",
							e.Message,
							leagueName + "-" + teamName,
							DateTime.UtcNow.ToString("s"),
							e.StackTrace),
						"Error");
					WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest;

				}		// try/catch

			}		// (!SecurityHelper.IsUserAuthenticated(WebOperationContext.Current))

			return result;

		}		// string CheckDataLast(XmlDocument appDataLast)