Skip to content

Commit d3a8eae

Browse files
Modify Azure Function App + Service Bus sample (#71)
1 parent 389771d commit d3a8eae

4 files changed

Lines changed: 200 additions & 35 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bld/
2222
[Oo]bj/
2323
[Ll]og/
2424
[Ll]ogs/
25+
publish/
2526

2627
# .NET Core
2728
project.lock.json

samples/function-app-service-bus/dotnet/README.md

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,99 @@ All deployment methods have been fully tested against Azure and the LocalStack f
8181
8282
## Test
8383

84-
Once the resources and function app have been deployed, you can use the [call-http-trigger.sh](./scripts/call-http-trigger.sh) Bash script to invoke the **GetGreetings** HTTP-triggered function. This function returns the most recent greetings stored in the in-memory circular buffer, allowing you to verify that the entire message pipeline is working end to end.
84+
Once the resources and function app have been deployed, you can use the [call-http-trigger.sh](./scripts/call-http-trigger.sh) Bash script to invoke the **GetGreetings** HTTP-triggered function. This function returns the most recent greetings stored in the in-memory circular buffer, allowing you to verify that the entire message pipeline is working end to end. The output should look like this:
85+
86+
```bash
87+
Getting function app name...
88+
Function app [local-func-test] successfully retrieved.
89+
Getting resource group name for function app [local-func-test]...
90+
Resource group [local-rg] successfully retrieved.
91+
Getting the default host name of the function app [local-func-test]...
92+
Function app default host name [local-func-test.azurewebsites.azure.localhost.localstack.cloud:4566] successfully retrieved.
93+
Finding container name with prefix [ls-local-func-test]...
94+
Looking for containers with names starting with [ls-local-func-test]...
95+
Found matching container [ls-local-func-test-tdkqjh]
96+
Container [ls-local-func-test-tdkqjh] found successfully
97+
Getting IP address for container [ls-local-func-test-tdkqjh]...
98+
IP address [172.17.0.7] retrieved successfully for container [ls-local-func-test-tdkqjh]
99+
Getting the host port mapped to internal port 80 in container [ls-local-func-test-tdkqjh]...
100+
Mapped host port [42330] retrieved successfully for container [ls-local-func-test-tdkqjh]
101+
Calling HTTP trigger function to retrieve the last [10] greetings via emulator...
102+
{
103+
"requester": {
104+
"sent": [
105+
"Paolo",
106+
"Max"
107+
]
108+
},
109+
"handler": {
110+
"received": [
111+
"Paolo",
112+
"Max"
113+
],
114+
"sent": [
115+
"Welcome Paolo, glad you're here!",
116+
"Salutations Max, how's everything going?"
117+
]
118+
},
119+
"consumer": {
120+
"received": [
121+
"Welcome Paolo, glad you're here!",
122+
"Salutations Max, how's everything going?"
123+
]
124+
}
125+
}
126+
Calling HTTP trigger function to retrieve the last [10] greetings via container IP address [172.17.0.7]...
127+
{
128+
"requester": {
129+
"sent": [
130+
"Paolo",
131+
"Max"
132+
]
133+
},
134+
"handler": {
135+
"received": [
136+
"Paolo",
137+
"Max"
138+
],
139+
"sent": [
140+
"Welcome Paolo, glad you're here!",
141+
"Salutations Max, how's everything going?"
142+
]
143+
},
144+
"consumer": {
145+
"received": [
146+
"Welcome Paolo, glad you're here!",
147+
"Salutations Max, how's everything going?"
148+
]
149+
}
150+
}
151+
Calling HTTP trigger function to retrieve the last [10] greetings via host port [42330]...
152+
{
153+
"requester": {
154+
"sent": [
155+
"Paolo",
156+
"Max"
157+
]
158+
},
159+
"handler": {
160+
"received": [
161+
"Paolo",
162+
"Max"
163+
],
164+
"sent": [
165+
"Welcome Paolo, glad you're here!",
166+
"Salutations Max, how's everything going?"
167+
]
168+
},
169+
"consumer": {
170+
"received": [
171+
"Welcome Paolo, glad you're here!",
172+
"Salutations Max, how's everything going?"
173+
]
174+
}
175+
}
176+
```
85177

86178
You can also inspect the function app's runtime behavior by viewing the logs of its Docker container. Run `docker logs ls-local-func-test-xxxxxx` (replacing `xxxxxx` with the actual container suffix) to see output similar to the following:
87179

samples/function-app-service-bus/dotnet/scripts/deploy.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,26 @@ if [ $DEPLOY -eq 0 ]; then
716716
exit 0
717717
fi
718718

719+
# Check if the application insights az extension is already installed
720+
echo "Checking if [application-insights] az extension is already installed..."
721+
az extension show --name application-insights &>/dev/null
722+
723+
if [[ $? == 0 ]]; then
724+
echo "[application-insights] az extension is already installed"
725+
else
726+
echo "[application-insights] az extension is not installed. Installing..."
727+
728+
# Install application-insights az extension
729+
az extension add --name application-insights 1>/dev/null
730+
731+
if [[ $? == 0 ]]; then
732+
echo "[application-insights] az extension successfully installed"
733+
else
734+
echo "Failed to install [application-insights] az extension"
735+
exit
736+
fi
737+
fi
738+
719739
# Check if the application insights component already exists
720740
echo "Checking if [$APPLICATION_INSIGHTS_NAME] Application Insights component exists in the [$RESOURCE_GROUP_NAME] resource group..."
721741
az monitor app-insights component show \

samples/function-app-service-bus/dotnet/src/GreetingFunctions.cs

Lines changed: 86 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ public class HelloWorld
4646

4747
private static readonly Random _random = new();
4848

49-
// Circular buffer storing the last 100 greetings
50-
private const int MaxGreetingHistory = 100;
51-
private static readonly string[] _greetingHistory = new string[MaxGreetingHistory];
52-
private static int _greetingIndex = 0;
53-
private static int _greetingCount = 0;
54-
private static readonly object _greetingLock = new();
49+
// Circular buffers for message history across all functions
50+
private const int MaxHistory = 100;
51+
private static readonly object _historyLock = new();
52+
private static readonly CircularBuffer _requesterSent = new(MaxHistory);
53+
private static readonly CircularBuffer _handlerReceived = new(MaxHistory);
54+
private static readonly CircularBuffer _handlerSent = new(MaxHistory);
55+
private static readonly CircularBuffer _consumerReceived = new(MaxHistory);
5556

5657
// Static initialization - runs once per application lifetime
5758
private static readonly Lazy<bool> _initialized = new Lazy<bool>(() => { Initialize(); return true; });
@@ -116,7 +117,7 @@ private static void Initialize()
116117
}
117118
}
118119

119-
/// <summary>
120+
/// <summary>
120121
/// Validates that all required configuration values are present and not empty.
121122
/// With default values in place, only the connection string is mandatory.
122123
/// </summary>
@@ -214,6 +215,12 @@ private static bool IsConfigurationValid()
214215

215216
_logger.LogInformation("[GreetingHandler] Processing request for name: {name}", requestMessage.Name);
216217

218+
// Record received name in history
219+
lock (_historyLock)
220+
{
221+
_handlerReceived.Add(requestMessage.Name);
222+
}
223+
217224
// Create the response message
218225
var greetingText = GetGreeting(requestMessage.Name);
219226
var outputObj = new ResponseMessage
@@ -287,6 +294,12 @@ public async Task GreetingRequesterAsync([TimerTrigger("%TIMER_SCHEDULE%", RunOn
287294
_logger.LogInformation("[GreetingRequester] Sending message to input queue '{inputQueue}'...", _inputQueueName);
288295
await sender.SendMessageAsync(serviceBusMessage);
289296
_logger.LogInformation("[GreetingRequester] Successfully sent message to input queue '{inputQueue}' with name: {Name}", _inputQueueName, selectedName);
297+
298+
// Record sent name in history
299+
lock (_historyLock)
300+
{
301+
_requesterSent.Add(selectedName);
302+
}
290303
}
291304
catch (Exception ex)
292305
{
@@ -320,7 +333,7 @@ public async Task GreetingConsumerAsync([TimerTrigger("%TIMER_SCHEDULE%", RunOnS
320333
}
321334

322335
try
323-
{
336+
{
324337
// Create Service Bus client for receiving messages from the output queue
325338
_logger.LogInformation("[GreetingConsumer] Creating Service Bus client for receiving messages...");
326339
await using var client = _hasClientId && _hasFullyQualifiedNamespace
@@ -364,6 +377,12 @@ public async Task GreetingConsumerAsync([TimerTrigger("%TIMER_SCHEDULE%", RunOnS
364377

365378
// Complete the message after successful processing
366379
await receiver.CompleteMessageAsync(receivedMessage);
380+
381+
// Record received greeting in history
382+
lock (_historyLock)
383+
{
384+
_consumerReceived.Add(responseMessage.Text);
385+
}
367386
}
368387
else
369388
{
@@ -387,17 +406,18 @@ public async Task GreetingConsumerAsync([TimerTrigger("%TIMER_SCHEDULE%", RunOnS
387406
finally
388407
{
389408
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
390-
try
391-
{
392-
await receiver.CloseAsync(cts.Token);
409+
try
410+
{
411+
await receiver.CloseAsync(cts.Token);
393412
}
394413
catch
395414
{ /* timeout or error on close */ }
396-
try
397-
{
398-
await client.DisposeAsync();
399-
}
400-
catch { /* benign */
415+
try
416+
{
417+
await client.DisposeAsync();
418+
}
419+
catch
420+
{ /* benign */
401421
}
402422
}
403423
}
@@ -421,12 +441,9 @@ private static string GetGreeting(string name)
421441
var template = _greetingTemplates[_random.Next(_greetingTemplates.Length)];
422442
var greeting = string.Format(template, name);
423443

424-
lock (_greetingLock)
444+
lock (_historyLock)
425445
{
426-
_greetingHistory[_greetingIndex] = greeting;
427-
_greetingIndex = (_greetingIndex + 1) % MaxGreetingHistory;
428-
if (_greetingCount < MaxGreetingHistory)
429-
_greetingCount++;
446+
_handlerSent.Add(greeting);
430447
}
431448

432449
return greeting;
@@ -444,31 +461,66 @@ public async Task<HttpResponseData> GetGreetingsAsync(
444461
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "greetings")] HttpRequestData request,
445462
int count = 20)
446463
{
447-
_logger.LogInformation("[GetGreetings] Retrieving last {count} greetings.", count);
464+
_logger.LogInformation("[GetGreetings] Retrieving last {count} entries.", count);
448465

449466
// Clamp count to valid range
450467
if (count < 1) count = 1;
451-
if (count > MaxGreetingHistory) count = MaxGreetingHistory;
468+
if (count > MaxHistory) count = MaxHistory;
452469

453-
string[] result;
454-
lock (_greetingLock)
470+
object history;
471+
lock (_historyLock)
455472
{
456-
var available = Math.Min(count, _greetingCount);
457-
result = new string[available];
458-
459-
// Read backwards from the most recent entry
460-
for (int i = 0; i < available; i++)
473+
history = new
461474
{
462-
var idx = (_greetingIndex - 1 - i + MaxGreetingHistory) % MaxGreetingHistory;
463-
result[i] = _greetingHistory[idx];
464-
}
475+
requester = new
476+
{
477+
sent = _requesterSent.ToArray(count)
478+
},
479+
handler = new
480+
{
481+
received = _handlerReceived.ToArray(count),
482+
sent = _handlerSent.ToArray(count)
483+
},
484+
consumer = new
485+
{
486+
received = _consumerReceived.ToArray(count)
487+
}
488+
};
465489
}
466490

467491
var response = request.CreateResponse(HttpStatusCode.OK);
468492
response.Headers.Add("Content-Type", "application/json");
469-
await response.WriteStringAsync(JsonSerializer.Serialize(result));
493+
await response.WriteStringAsync(JsonSerializer.Serialize(history));
470494
return response;
471495
}
496+
497+
private sealed class CircularBuffer
498+
{
499+
private readonly string[] _items;
500+
private int _index;
501+
private int _count;
502+
503+
public CircularBuffer(int capacity) => _items = new string[capacity];
504+
505+
public void Add(string item)
506+
{
507+
_items[_index] = item;
508+
_index = (_index + 1) % _items.Length;
509+
if (_count < _items.Length) _count++;
510+
}
511+
512+
public string[] ToArray(int count)
513+
{
514+
var available = Math.Min(count, _count);
515+
var result = new string[available];
516+
for (int i = 0; i < available; i++)
517+
{
518+
var idx = (_index - available + i + _items.Length) % _items.Length;
519+
result[i] = _items[idx];
520+
}
521+
return result;
522+
}
523+
}
472524
}
473525

474526
/// <summary>

0 commit comments

Comments
 (0)