I try to unit test a rather basic scenario - worker queue with 2 workers and 1 publisher scenario, but it keeps returning the same message over and over again from the queue.
The following code in the test just puts the 1 to 100 messages to the queue and 2 consumers eat them up. The problem is that they keep just getting message 1 and 2. I tried to separate the acknowledgement into a method, since in my application it takes time for a message to get process (commented method Confirm) - then it threw an exception that token is unknown:
The AMQP operation was interrupted: AMQP close-reason, initiated by
Peer, code=406, text="PRECONDITION_FAILED - unknown delivery tag 1",
classId=60, methodId=80, cause=
It seems that the acknowledgement is broken somehow. I tried to switch it off - no luck either.
Class:
using System;
using System.Text;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
namespace Backend.MQ.OCR
{
public class BatchQueue : QueueBase<BatchMessage>
{
private readonly IModel _channel;
private const string QPrefix = "ocrbatches_";
private readonly QueueingBasicConsumer _consumer;
private ulong _latesttoken = ulong.MaxValue;
private readonly string _jobid;
public BatchQueue(string connectionString, String jobid):
base(connectionString)
{
_jobid = jobid;
var factory = new ConnectionFactory()
{
HostName = connectionString
};
var connection = factory.CreateConnection();
_channel = connection.CreateModel();
_channel.QueueDeclare(Name, true, false, false, null);
//binding consumers
_channel.BasicQos(0, 1, false);
_consumer = new QueueingBasicConsumer(_channel);
_channel.BasicConsume(Name, false, _consumer);
}
public override void Publish(BatchMessage msg)
{
var message = JsonConvert.SerializeObject(msg);
var body = Encoding.UTF8.GetBytes(message);
var properties = _channel.CreateBasicProperties();
properties.SetPersistent(true);
_channel.BasicPublish("", Name, properties, body);
#if DEBUG
System.Diagnostics.Trace.WriteLine("[x] Sent task:" + msg);
#endif
}
private string Name
{
get { return QPrefix + _jobid; }
}
public override BatchMessage Receive()
{
var ea =
(BasicDeliverEventArgs)_consumer.Queue.Dequeue();
var body = ea.Body;
_channel.BasicAck(ea.DeliveryTag, false);
return JsonConvert.DeserializeObject<BatchMessage>(Encoding.UTF8.GetString(body));
}
public override void Confirm()
{
//if (_latesttoken < ulong.MaxValue) _channel.BasicAck(_latesttoken, false);
}
}
}
Unit tests:
#if NUNIT
using TestClass = NUnit.Framework.TestFixtureAttribute;
using TestMethod = NUnit.Framework.TestAttribute;
using TestCleanup = NUnit.Framework.TearDownAttribute;
using TestInitialize = NUnit.Framework.SetUpAttribute;
using ClassCleanup = NUnit.Framework.TestFixtureTearDownAttribute;
using ClassInitialize = NUnit.Framework.TestFixtureSetUpAttribute;
#else
#endif
using System.Threading.Tasks;
using System.Threading;
using System;
using System.Collections.Generic;
using Backend.MQ.OCR;
using Microsoft.VisualStudio.TestTools.UnitTesting;
#if NUNIT
using MAssert = NUnit.Framework.Assert;
#else
using MAssert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
#endif
namespace MQ.Test
{
[TestClass]
public class BatchQueueTest
{
[TestMethod]
public void Concurrencytest()
{
var batchname = Guid.NewGuid().ToString();
var queue = new BatchQueue("localhost", batchname);
var tasks = new List<Task>();
var counter = 0;
for (int i = 0; i < 100; i++)
{
queue.Publish(new BatchMessage()
{
Files = new List<string>() { i.ToString() }
});
}
for (int i = 0; i < 2; i++)
{
var task = Task.Factory.StartNew(() =>
{
var q = new BatchQueue("localhost", batchname);
var res = q.Receive();
while (res != null)
{
System.Diagnostics.Trace.WriteLine(res.Files[0]);
q.Confirm();
Interlocked.Increment(ref counter);
}
});
tasks.Add(task);
}
var ok = Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(30));
MAssert.IsTrue(ok, "Tasks didnt complete in time");
MAssert.AreEqual(counter, 100, "Not all messages have been processed");
}
}
}
Your unit test starts two tasks. Before the while loop you receive a message but you keep confirming the same message inside the while loop:
var q = new BatchQueue("localhost", batchname);
//Receive message 1 or 2
var res = q.Receive();
while (res != null)
{ //Infinite loop
System.Diagnostics.Trace.WriteLine(res.Files[0]);
q.Confirm();
Interlocked.Increment(ref counter);
}
Try to put var res = q.Receive(); inside the loop
Related
Since I am only allowed 10 event hubs per namespace, I am looking for a good algorithm to create a new namespace for every ten event hubs in the list I have.
NOTE: The list of event hub namespaces are being passed in to the method.
var eventHubResources = GetRequiredService<List<EventHubResource>>();
foreach (var eventHubResource in eventHubResources)
{
eventHubResource.ResourceGroup = resourceGroup;
MyNamespace.IEventHub eventHub = new EventHub(logger);
if (eventHubResource.CaptureSettings == null)
{
if (eventHubResources.IndexOf(eventHubResource) <= 9)
{
await eventHub.CreateEventHubAsync(azure, eventHubNamespace[0], eventHubResource, null);
}
if ((eventHubResources.IndexOf(eventHubResource) > 9) && (eventHubResources.IndexOf(eventHubResource) <= 19))
{
await eventHub.CreateEventHubAsync(azure, eventHubNamespace[1], eventHubResource, null);
}
// and so on....
}
else
{
await eventHub.CreateEventHubAsync(azure, eventHubNamespace, eventHubResource, storageAccount);
}
}
What is a better way to achieve this, compared to what I have above?
I didn't test the code but you can get the idea.
public static async Task CreateNamespaces(List<string> eventhubNames, ServiceClientCredentials creds) {
int totalEventHubsInNamespace = 0;
var ehClient = new EventHubManagementClient(creds)
{
SubscriptionId = "<my subscription id>"
};
foreach (var ehName in eventhubNames)
{
if (totalEventHubsInNamespace == 0)
{
var namespaceParams = new EHNamespace()
{
Location = "<datacenter location>"
};
// Create namespace
var namespaceName = "<populate some unique namespace>";
Console.WriteLine($"Creating namespace... {namespaceName}");
await ehClient.Namespaces.CreateOrUpdateAsync(resourceGroupName, namespaceName, namespaceParams);
Console.WriteLine("Created namespace successfully.");
}
// Create eventhub.
Console.WriteLine($"Creating eventhub {ehName}");
var ehParams = new Eventhub() { }; // Customize you eventhub here if you need.
await ehClient.EventHubs.CreateOrUpdateAsync(resourceGroupName, namespaceName, ehName, ehParams);
Console.WriteLine("Created Event Hub successfully.");
totalEventHubsInNamespace++;
if (totalEventHubsInNamespace >= 10)
{
totalEventHubsInNamespace = 0;
}
}
}
I make SOAP requests to an e-commerce API to know how many orders were made for a specified product, in a given time frame.
My method takes a product ID and a number of days to create a time frame. Then it constructs a SOAP request and returns the amount sold products.
public static async Task<int?> GetOrdersFromApi(int providedId, int days) {
var dateFrom = DateTime.Now.AddDays(days * -1).ToString("yyyy-MM-dd") + " 00:00:00";
var dateTo = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
float orderedCount = 0;
int orderedCountToPass = 0;
int i = 0;
var binding = new BasicHttpBinding {
MaxReceivedMessageSize = 40000000
};
var address = new EndpointAddress("http://example.com/api/get/");
using (var client = new ApiOrdersPortTypeClient(binding, address)) {
try {
while (true) {
// request parameters
var request = new ApiOrdersGetAsync.requestType {
authenticate = new ApiOrdersGetAsync.authenticateType {
userLogin = "Username",
authenticateKey = "Key",
}
};
request.#params = new ApiOrdersGetAsync.paramsType {
ordersStatuses = new string[] { "finished" },
products = new productType[1],
};
request.#params.products[0] = new productType {
productIdSpecified = true,
productId = providedId,
};
request.#params.ordersRange = new ordersRangeType {
ordersDateRange = new ordersDateRangeType()
};
request.#params.ordersRange.ordersDateRange.ordersDateTypeSpecified = true;
request.#params.ordersRange.ordersDateRange.ordersDateType = ordersDateTypeType.dispatch;
request.#params.ordersRange.ordersDateRange.ordersDateBegin = dateFrom;
request.#params.ordersRange.ordersDateRange.ordersDateEnd = dateTo;
request.#params.resultsPageSpecified = true;
request.#params.resultsPage = i;
// processing the result
var results = await client.getAsync(request);
foreach (var result in results.Results) {
int productsResultsPage = 0;
foreach (var product in result.orderDetails.productsResults) {
try {
if (result.orderDetails.productsResults[productsResultsPage ].productId == providedId) {
orderedCount += result.orderDetails.productsResults[y].productQuantity;
productsResultsPage++;
}
} catch (IndexOutOfRangeException ex) {
// error is thrown to escape loop - sloppy, I know
Console.WriteLine(ex);
};
}
};
orderedCountToPass = (int)orderedCount;
orderedCount = 0;
i++;
};
} catch (NullReferenceException) {
// do nothing, we just want to exit while loop when i is higher than page count
}
return orderedCountToPass;
};
}
The result often should be in hundreds, but regardless how well a product sells, it returns something from 0 to 4.
Here is a sample response:
For example, I'm only interested with productId == 479, but an order was made with other products as well that don't interest me - I need to filter them.
I'm doing something wrong with how I try to filter results. How do I do it properly? I'm certain the request is correct and response does contain all possible orders.
You are getting xml results so use Net Xml library :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string URL = #"Enter URL Here";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(URL);
List<XElement> items = doc.Descendants("item").ToList();
}
}
}
I wrote a fairly simple wrapper around the RestSharp client adding some retry logic to it using Polly.
I setup Fiddler to test it and simulated some "Bad" responses.
The problem I have is in the onRetry delegate, the result.Result.StatusCode bit seems to sometimes log as 0 instead of the actual bad status code (502 in some of my testings).
However, with my unit tests it seems that its working perfectly. Race conditions here maybe?
Any idea why this is happening?
Thanks in advance.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Polly;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Net;
namespace FundsAFE.Graphite
{
public class RequestExecutor
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private IRestClient client;
private IRestRequest request;
private Policy<IRestResponse> retryPolicy;
public IRestResponse LastErrorResponse { get; set; }
private static readonly List<HttpStatusCode> invalidStatusCodes = new List<HttpStatusCode> {
HttpStatusCode.BadGateway,
HttpStatusCode.Unauthorized,
HttpStatusCode.InternalServerError,
HttpStatusCode.RequestTimeout,
HttpStatusCode.BadRequest,
HttpStatusCode.Forbidden,
HttpStatusCode.GatewayTimeout
};
public RequestExecutor(IRestClient client, IRestRequest request)
{
this.client = client;
this.request = request;
}
public IRestResponse Execute(int retryCount, int delay)
{
retryPolicy = Policy
.HandleResult<IRestResponse>(resp => invalidStatusCodes.Contains(resp.StatusCode) || !IsValidJson(resp))
.WaitAndRetry(retryCount, i => TimeSpan.FromMilliseconds(delay), (result, timeSpan, currentRetryCount, context) =>
{
//Status code here is sometimes 0???
logger.Error($"Request failed with {result.Result.StatusCode}. Waiting {timeSpan} before next retry. Retry attempt {currentRetryCount}");
LastErrorResponse = result.Result;
});
var policyResponse = retryPolicy.ExecuteAndCapture(() =>
{
var url = client.BuildUri(request);
logger.Debug(url.ToString());
var response = client.Execute(request);
return response;
});
if(policyResponse.Result != null)
{
return policyResponse.Result;
} else
{
return LastErrorResponse;
}
}
public static bool IsValidJson(IRestResponse response)
{
if (response.Content.Length == 0)
{
//Empty response treated as invalid
return false;
}
try
{
var parsed = JObject.Parse(response.Content);
}
catch (JsonReaderException e)
{
//Will catch any mallformed json
return false;
}
return true;
}
}
}
using Microsoft.VisualStudio.TestTools.UnitTesting;
using FundsAFE.Graphite;
using Moq;
using RestSharp;
using System.Net;
using FluentAssertions;
using System;
using FluentAssertions.Extensions;
namespace FundsAFE.Test.Moq
{
[TestClass]
public class MoqUnitTestRequest
{
public Mock<IRestClient> CreateMockClientWithStatusCodeAndContent(HttpStatusCode code, string content)
{
Mock<IRestClient> mockClient = new Mock<IRestClient>();
mockClient.Setup(c => c.Execute(It.IsAny<IRestRequest>())).Returns(
new RestResponse
{
Content = content,
StatusCode = code
}
);
mockClient.Setup(c => c.BuildUri(It.IsAny<IRestRequest>())).Returns(
new Uri("http://fake.fake")
);
return mockClient;
}
[DataTestMethod]
[DataRow(HttpStatusCode.BadGateway)]
[DataRow(HttpStatusCode.Unauthorized)]
[DataRow(HttpStatusCode.InternalServerError)]
[DataRow(HttpStatusCode.RequestTimeout)]
[DataRow(HttpStatusCode.BadRequest)]
[DataRow(HttpStatusCode.Forbidden)]
[DataRow(HttpStatusCode.GatewayTimeout)]
public void TestBadStatusCodesAndRetry(HttpStatusCode httpStatusCode) {
//Arrange
Mock<IRestRequest> mockRequest = new Mock<IRestRequest>();
Mock<IRestClient> mockClient = CreateMockClientWithStatusCodeAndContent(httpStatusCode, "fakecontent");
RequestExecutor requestExecutor = new RequestExecutor(mockClient.Object, mockRequest.Object);
int retries = 10;
int delay = 50;
int totalWaitTime = (retries * delay) - 10; //10ms error margin
//Act and Verify
var response = requestExecutor.Execute(retryCount: retries, delay: 101);
mockClient.Verify(x => x.Execute(It.IsAny<IRestRequest>()), Times.Exactly(retries + 1)); //1st failed attempt + 10 retries = 11
//Assert
requestExecutor.ExecutionTimeOf(re => re.Execute(retries, delay)).Should().BeGreaterOrEqualTo(totalWaitTime.Milliseconds());
response.Should().NotBeNull();
response.StatusCode.Should().Be(httpStatusCode);
requestExecutor.LastErrorResponse.StatusCode.Should().Be(httpStatusCode);
}
[DataTestMethod]
//Empty content
[DataRow("")]
//Missing closing quote
[DataRow("{\"fruit\": \"Apple,\"size\": \"Large\",\"color\": \"Red\"}")]
//Missing angle bracket
[DataRow("\"q1\": {\"question\": \"Which one is correct team name in NBA?\",\"options\": \"New York Bulls\",\"Los Angeles Kings\",\"Golden State Warriros\",\"Huston Rocket\"],\"answer\": \"Huston Rocket\"}")]
//Missing curly bracket
[DataRow("\"sport\": {\"q1\": {\"question\": \"Which one is correct team name in NBA?\",\"options\": \"New York Bulls\",\"Los Angeles Kings\",\"Golden State Warriros\",\"Huston Rocket\"],\"answer\": \"Huston Rocket\"}")]
public void TestBadContentRetries(string content)
{
//Arrange
Mock<IRestRequest> mockRequest = new Mock<IRestRequest>();
Mock<IRestClient> mockClient = CreateMockClientWithStatusCodeAndContent(HttpStatusCode.OK, content);
RequestExecutor requestExecutor = new RequestExecutor(mockClient.Object, mockRequest.Object);
int retries = 10;
int delay = 50;
int totalWaitTime = (retries * delay) - 10; //10ms error margin
//Act and Verify
var response = requestExecutor.Execute(retryCount: retries, delay: delay);
mockClient.Verify(x => x.Execute(It.IsAny<IRestRequest>()), Times.Exactly(retries + 1)); //1st failed attempt + 10 retries = 11
//Assert
requestExecutor.ExecutionTimeOf(re => re.Execute(retries, delay)).Should().BeGreaterOrEqualTo(totalWaitTime.Milliseconds());
response.Should().NotBeNull();
}
}
}
It looks from the RESTsharp source code as if there are cases (eg some exception cases) where RESTsharp might well return you an IRestResponse with resp.StatusCode == 0.
The logging code in onRetry should probably be checking the wider set of status properties on IRestResponse, not just IRestResponse.StatusCode
I'm trying to find all the builds and releases that are configured to use a specific agent pool, using the .NET Client Libraries.
Assuming agentPoolId, I can get all the build definitions like this:
// _connection is of type VssConnection
using (var buildClient = _connection.GetClient<BuildHttpClient>())
{
List<BuildDefinitionReference> allBuilds = await buildClient.GetDefinitionsAsync(projectName, top: 1000, queryOrder: DefinitionQueryOrder.DefinitionNameAscending);
List<BuildDefinitionReference> builds = allBuilds.Where(x => HasAgentPoolId(x, agentPoolId)).ToList();
}
private bool HasAgentPoolId(BuildDefinitionReference buildDefinition, int agentPoolId)
{
TaskAgentPoolReference pool = buildDefinition?.Queue?.Pool;
if (pool == null)
{
return false;
}
return pool.Id.Equals(agentPoolId);
}
But I couldn't find a way to find the release definitions that have one or more environments configured to use a particular agent. Any suggestion?
I was manged to get all releases by Agent Pool ID via Rest Api and not via NET Client Libraries.Hope that helps.
C# Code snippet:
public class ReleaseResponse
{
[JsonProperty("value")]
public List<ReleaseItem> Value { get; set; }
}
public class ReleaseItem
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("Id")]
public int Id { get; set; }
}
static void Main(string[] args)
{
string tfsURL = "TFS URL";
string releaseDefurl = $"{tfsURL}/_apis/release/definitions?$expand=artifacts&api-version=3.2-preview.3";
const int agentPoolID = "AGENT Pool ID";
List<string> relevantReleases = new List<string>();
WebClient client = new WebClient();
client.UseDefaultCredentials = true;
client.Headers.Add("Content-Type", "application/json");
var releaseList = client.DownloadString(releaseDefurl);
var allReleases = JsonConvert.DeserializeObject<ReleaseResponse>(releaseList).Value;
foreach (var release in allReleases)
{
string releaseInfoApi = $"{tfsURL}/_apis/Release/definitions/{release.Id}";
var getReleseInfo = client.DownloadString(releaseInfoApi);
var releaseInfo = JsonConvert.DeserializeObject<TFSLogic.RootObject>(getReleseInfo);
var deploymentAgents = releaseInfo.environments.ToList().Where(e => e.deployPhases.FirstOrDefault().deploymentInput.queueId == agentPoolID).Count();
if (deploymentAgents > 0)
{
relevantReleases.Add(release.Name);
}
}
}
Find TFSLogic here : https://codebeautify.org/online-json-editor/cb7aa0d9
Powershell Code snippet:
$tfsUrl = "TFS URL"
$releaseDefurl = $tfsUrl + '/_apis/release/definitions?$expand=artifacts&api-version=3.2-preview.3'
$agentPoolID = "Agent Pool ID"
$relevantReleases = #();
$allReleasesID = (Invoke-RestMethod -Uri ($releaseDefurl) -Method Get -UseDefaultCredentials).value.id
function getReleaseByAgentPoolID($releaseID,$agentPoolID)
{
$ReleaseInfo = Invoke-RestMethod -Uri "$tfsUrl/_apis/Release/definitions/$releaseID" -Method Get -UseDefaultCredentials
$deploymentAgents = $ReleaseInfo.environments | % {$_.deployPhases.deploymentInput.queueId} | where {$_ -eq $agentPoolID}
if($deploymentAgents.Count -gt 0)
{
return $ReleaseInfo.name
}
}
foreach ($releaseID in $allReleasesID)
{
$relevantReleases += getReleaseByAgentPoolID -releaseID $releaseID -agentPoolID $agentPoolID
}
UPDATE :
It took me some time,But i was able to achieve that with azure-devops-dotnet-samples
I hope this example is finally what you are looking for.
using Microsoft.VisualStudio.Services.WebApi;
using System;
using System.Linq;
using Microsoft.VisualStudio.Services.ReleaseManagement.WebApi.Clients;
using Microsoft.VisualStudio.Services.ReleaseManagement.WebApi.Contracts;
using Microsoft.VisualStudio.Services.Common;
using System.Collections.Generic;
namespace FindReleaseByAgentPoolID
{
class Program
{
const int agentPoolID = 999;
static void Main(string[] args)
{
var relevantReleases = new List<string>();
VssCredentials c = new VssCredentials(new WindowsCredential(System.Net.CredentialCache.DefaultNetworkCredentials));
var tfsURL = new Uri("TFS URL");
var teamProjectName = "PROJECT";
using (var connection = new VssConnection(tfsURL, c))
using (var rmClient = connection.GetClient<ReleaseHttpClient2>())
{
var releases = rmClient
.GetReleaseDefinitionsAsync(teamProjectName, string.Empty, ReleaseDefinitionExpands.Environments)
.Result.ToArray();
foreach (var release in releases)
{
var r = rmClient.GetReleaseDefinitionAsync(teamProjectName, release.Id);
var deploymentAgents = r.Result.Environments.SelectMany(e =>
e.DeployPhases.Select(dp =>
dp.GetDeploymentInput()).Cast<DeploymentInput>()).Where(di =>
di.QueueId == agentPoolID).Count();
if (deploymentAgents > 0)
{
relevantReleases.Add(release.Name);
}
}
}
}
}
}
Found a solution, many thanks to #amit-baranes for pointing me in the right direction.
I've changed his code sample to use the await keyword instead of using .Result, and use .OfType<DeploymentInput>() instead of .Cast<DeploymentInput>() (it was throwing some exceptions).
Oh, and the most important thing I've learned: agent pool ID and queue ID are different things!!! If you intend to use the agent pool ID to get the release definitions you'll need to get the correspondent agent queue.
Code sample:
// set agent pool Id and project name
int agentPoolId = 123456;
string teamProjectName = ".....";
// _connection is of type VssConnection
using (var taskAgentClient = _connection.GetClient<TaskAgentHttpClient>())
using (var releaseClient = _connection.GetClient<ReleaseHttpClient2>())
{
// please note: agent pool Id != queue Id
// agent pool id is used to get the build definitions
// queue Id is used to get the release definitions
TaskAgentPool agentPool = await taskAgentClient.GetAgentPoolAsync(agentPoolId);
List<TaskAgentQueue> queues = await taskAgentClient.GetAgentQueuesByNamesAsync(teamProjectName, queueNames: new[] { agentPool.Name });
TaskAgentQueue queue = queues.FirstOrDefault();
List<ReleaseDefinition> definitions = await releaseClient.GetReleaseDefinitionsAsync(teamProjectName, string.Empty, ReleaseDefinitionExpands.Environments);
foreach (ReleaseDefinition definition in definitions)
{
var fullDefinition = await releaseClient.GetReleaseDefinitionAsync(teamProjectName, definition.Id);
bool hasReleasesWithPool = fullDefinition.Environments.SelectMany(GetDeploymentInputs)
.Any(di => di.QueueId == queue.Id);
if (hasReleasesWithPool)
{
Debug.WriteLine($"{definition.Name}");
}
}
}
private IEnumerable<DeploymentInput> GetDeploymentInputs(ReleaseDefinitionEnvironment environment)
{
return environment.DeployPhases.Select(dp => dp.GetDeploymentInput())
.OfType<DeploymentInput>();
}
I found the example on the internet. Somehow, I cannot make it run correctly.
I did a lot of search but still cannot find out what is the mistake?!
Hope, someone can help to figure it out.
Server code:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Tracing;
using HTek.Core.MEF;
using HTek.Core.Server.Web.MEF;
using HTek.Core.Server.Web.Tracing;
using Microsoft.Owin;
using Owin;
// http://owin.org/extensions/owin-WebSocket-Extension-v0.4.0.htm
using WebSocketAccept = System.Action<System.Collections.Generic.IDictionary<string, object>, // options
System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>>; // callback
using WebSocketCloseAsync = System.Func<int /* closeStatus */,
string /* closeDescription */,
System.Threading.CancellationToken /* cancel */,
System.Threading.Tasks.Task>;
using WebSocketReceiveAsync = System.Func<System.ArraySegment<byte> /* data */,
System.Threading.CancellationToken /* cancel */, System.Threading.Tasks.Task<System.Tuple<int /* messageType */,
bool /* endOfMessage */,
int /* count */>>>;
using WebSocketSendAsync = System.Func<System.ArraySegment<byte> /* data */,
int /* messageType */,
bool /* endOfMessage */,
System.Threading.CancellationToken /* cancel */,
System.Threading.Tasks.Task>;
using WebSocketReceiveResult = System.Tuple<int, // type
bool, // end of message?
int>; // count
namespace Server.WebApi
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var webApiConfiguration = ConfigureWebApi();
// Use the extension method provided by the WebApi.Owin library:
app.UseWebApi((HttpConfiguration)webApiConfiguration);
app.Use(UpgradeToWebSockets);
}
private HttpConfiguration ConfigureWebApi()
{
WebComposition provider = new WebComposition();
Composition.SetCurrentProvider(provider);
Composition.Start();
var config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new
{
id = RouteParameter.Optional
});
provider.ConfigureWebApi(config);
//// tracing config
config.Services.Replace(typeof(ITraceWriter), new Tracer());
return config;
}
private static Task UpgradeToWebSockets(IOwinContext context, Func<Task> next)
{
WebSocketAccept accept = context.Get<WebSocketAccept>("websocket.Accept");
if (accept == null)
{
// Not a websocket request
return next();
}
accept(null, WebSocketEcho);
return Task.FromResult<object>(null);
}
private static async Task WebSocketEcho(IDictionary<string, object> websocketContext)
{
var sendAsync = (WebSocketSendAsync)websocketContext["websocket.SendAsync"];
var receiveAsync = (WebSocketReceiveAsync)websocketContext["websocket.ReceiveAsync"];
var closeAsync = (WebSocketCloseAsync)websocketContext["websocket.CloseAsync"];
var callCancelled = (CancellationToken)websocketContext["websocket.CallCancelled"];
byte[] buffer = new byte[1024];
WebSocketReceiveResult received = await receiveAsync(new ArraySegment<byte>(buffer), callCancelled);
object status;
while (!websocketContext.TryGetValue("websocket.ClientCloseStatus", out status)
|| (int)status == 0)
{
// Echo anything we receive
await
sendAsync(new ArraySegment<byte>(buffer, 0, received.Item3),
received.Item1,
received.Item2,
callCancelled);
received = await receiveAsync(new ArraySegment<byte>(buffer), callCancelled);
}
await
closeAsync((int)websocketContext["websocket.ClientCloseStatus"],
(string)websocketContext["websocket.ClientCloseDescription"],
callCancelled);
}
}
}
private static void Main(string[] args)
{
string baseAddress = "http://localhost:9000/";
// Start OWIN host
using (WebApp.Start<Startup>(url: baseAddress))
{
Logger<Program>.InfoFormat("Server running on {0}", baseAddress);
Console.ReadLine();
}
}
And HTML client:
var wsUri = "ws://localhost:9000";
var output;
function init()
{
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt)
{
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
}
function onClose(evt)
{
writeToScreen("DISCONNECTED");
}
function onMessage(evt)
{
writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
websocket.close();
}
function onError(evt)
{
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message)
{
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message)
{
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
Everything looks good. Excepted, 'accept'(in 'UpgradeToWebSockets') is always NULL.
WebSocket is not supports in old Windows releases such as Windows 7.
You might upgrade to a newer release such as Windows 10.