I'm trying to implement the ASP.NET Core 2.2 health check feature. Setting up the health check itself isn't the problem, but I also want to be able to use the UI feature in other project to monitoring all my apis. Right now I get the exception message
Unexpected character encountered while parsing value: <.
What I'm doing bad?
API Project:
var healthCheckOptions = new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = async (c, r) =>
{
c.Response.ContentType = MediaTypeNames.Application.Json;
var result = JsonConvert.SerializeObject(
new
{
Checks = r.Entries.Select(e =>
new
{
Description = e.Key,
Status = e.Value.Status.ToString(),
ResponseTime = e.Value.Duration.TotalMilliseconds
}),
TotalResponseTime = r.TotalDuration.TotalMilliseconds
});
await c.Response.WriteAsync(result);
}
};
app.UseHealthChecks("/live", new HealthCheckOptions
{
Predicate = _ => true
});
app.UseHealthChecks("/hc", healthCheckOptions);
app.UseHealthChecksUI(options => options.UIPath = "/healtcheck");
// Registers required services for health checks
services
.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy())
.AddCheck("ComunContext Database", new SqlServerHealthCheck(configuration["ConnectionStrings:ComunContext"]));
Web project:
services.AddHealthChecksUI();
app.UseHealthChecksUI(config =>
{
config.UIPath = "/healthcheck";
});
appsettings.json
{
"HealthChecks-UI": {
"HealthChecks": [
{
"Name": "Local",
"Uri": "http://localhost:27365/hc"
}
],
"EvaluationTimeOnSeconds": 10,
"MinimumSecondsBetweenFailureNotifications": 60
}
}
Try adding a ResponseWriter:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHealthChecks("/healthchecks", new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json; charset=utf-8";
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report));
await context.Response.Body.WriteAsync(bytes);
}
});
app.UseHealthChecksUI();
}
After a few days struggling with this parser error, I've figured out that there are 2 problems:
1 - If you have an Exception, Health UI tries to convert Exception object field, resulting on error;
2 - If you try to pass your own anonymous object, Health UI fails to convert Entries collection, because it need to be an specific anonymous Dictionary.
Try this:
var healthCheckOptions = new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = async (c, r) =>
{
c.Response.ContentType = MediaTypeNames.Application.Json;
var result = JsonConvert.SerializeObject(
new
{
Checks = r.Entries.ToDictionary(
e => e.Key,
e =>
new
{
Description = e.Key,
Status = e.Value.Status.ToString(),
ResponseTime = e.Value.Duration.TotalMilliseconds
}),
TotalResponseTime = r.TotalDuration.TotalMilliseconds
});
await c.Response.WriteAsync(result);
}
};
Related
I'm experimenting with gjs and webkit2, how can i get the http headers of a request made with load_uri
i have the following code
const Gtk = imports.gi.Gtk, WebKit=imports.gi.WebKit2, contentManager=new WebKit.UserContentManager,
view = WebKit.WebView.new_with_user_content_manager(contentManager);
Gtk.init(null);
let win = new Gtk.Window(), Response=new WebKit.URIResponse();
contentManager.add_script (new WebKit.UserScript("alert ('test');",0,1,null,null));
view.load_uri('https://www.gnome.org');
win.add(view);
win.set_title("test");
win.set_icon_from_file("/games/aptdaemon-resolve.png");
win.connect('destroy', () => { Gtk.main_quit(); });
win.set_size_request(640, 480);
win.show_all();
view.connect("load-changed",function (instance,state)
{
if (state == 3)
{
log ("URL"+Response.get_uri());
view.run_javascript ("alert (document.body.innerHTML)",null,null);
}
});
Gtk.main();
for example Response.get_uri returns an empty string, how to access response headers, and how to exchange messages between scripts injected with view.run_javascript and gjs. i want the body html be sent to gjs-?
got it
const Gtk = imports.gi.Gtk;
const WebKit=imports.gi.WebKit2;
Gtk.init(null);
const win = new Gtk.Window(), contentManager=new WebKit.UserContentManager, view = WebKit.WebView.new_with_user_content_manager(contentManager);
let response_STR;
contentManager.connect("script-message-received::pipe", function (instance, message)
{
message=message.get_js_value().to_string ();
log (message);
});
contentManager.register_script_message_handler("pipe");
view.load_uri('https://www.gnome.org');
win.add(view);
win.set_title("test");
win.connect('destroy', () => { Gtk.main_quit(); });
win.set_size_request(640, 480);
win.show_all();
view.connect("load-changed",function (instance,status)
{
let headers, response_STR="";
if (status == 3)
{
/* WebKitView.get_main_resource -> returns WebResource
WebResource.get_response -> returns URIResponse
URIResponse.get_http_headers -> returns Soup.MessageHeaders */
headers=view.get_main_resource().get_response().get_http_headers();
response_STR="";
headers.foreach ((name, value) => { response_STR+=name+": "+value+"\n"});
view.run_javascript('window.webkit.messageHandlers.pipe.postMessage(document.body.innerHTML);', null, null);
log (response_STR);
}
});
Gtk.main();
I'm trying to cover External auth with Unit Tests.
Mocked all dependencies including SignInAsync(). But execution fails with the message "No service for type 'IdentityServer4.Configuration.IdentityServerOptions' has been registered." when it hits SignInAsync().
Stack trace here
Test method.
public async Task CallBack_User_Is_Defined_In_AAD_ReturnChallenge()
{
var ticket = new AuthenticationTicket(TestUsers.Principal, "Test");
var result = AuthenticateResult.Success(ticket);
result.Properties?.Items.Add("returnUrl", "sign-callback");
result.Properties?.Items.Add("scheme", "aad");
authenticationService.Setup(c => c.AuthenticateAsync(It.IsAny<HttpContext>(), IdentityServerConstants.ExternalCookieAuthenticationScheme)).Returns(Task.FromResult(result)); ;
var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock
.Setup(_ => _.GetService(typeof(IAuthenticationService)))
.Returns(authenticationService.Object);
serviceProviderMock
.Setup(_ => _.GetService(typeof(IdentityOptions)))
.Returns(authenticationService.Object);
var identityServiceUserResponse = new IdentityServiceUserResponse
{
Email = TestUsers.Principal.FindFirstValue(ClaimTypes.Email),
CompanyID = "1",
Login = "john"
};
identityServiceAuthService.Setup(c => c.GetUserByExternalEmailAsync(It.IsAny<string>()))
.Returns(Task.FromResult(identityServiceUserResponse));
var authRequest = new AuthorizationRequest
{
Client = new Client { ClientId = "client" },
ValidatedResources = resourceValidationResult,
Parameters = { { "returnUrl", "signin-callback" } },
};
interactionService.Setup(i => i.GetAuthorizationContextAsync(It.IsAny<string>()))
.Returns(Task.FromResult(authRequest));
authenticationService
.Setup(_ => _.SignInAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<ClaimsPrincipal>(), It.IsAny<AuthenticationProperties>()))
.Returns(Task.FromResult((object)null));
controller = new ExternalController(configuration.Object, interactionService.Object,
identityServiceAuthService.Object, clientStore.Object, events.Object, logger.Object);
controller.ControllerContext = controller.CreateControllerContext(TestUsers.Principal);
controller.ControllerContext.HttpContext.RequestServices = serviceProviderMock.Object;
await controller.Callback();
}
I have a table that has more than 25 items and wrote a basic script to break them into sub arrays of 25 items each then loops thru that collection of sub arrays to run a batch write item command in the AWS DynamoDB Client. The issue I am getting is a returned validation error. When I run the same seed file via the aws-cli it seeds the table perfectly. This makes me think it has something to do with my script. See anything I am missing? Thanks in advance!
var { DynamoDB } = require('aws-sdk');
var db = new DynamoDB.DocumentClient({
region: 'localhost',
endpoint: 'http://localhost:8000',
});
const allItems = require('./allItems.json');
const tableName = 'some-table-name';
console.log({ tableName, allItems });
var batches = [];
var currentBatch = [];
var count = 0;
for (let i = 0; i < allItems.length; i++) {
//push item to the current batch
count++;
currentBatch.push(allItems[i]);
if (count === 25) {
batches.push(currentBatch);
currentBatch = [];
}
}
//if there are still items left in the curr batch, add to the collection of batches
if (currentBatch.length > 0 && currentBatch.length !== 25) {
batches.push(currentBatch);
}
var completedRequests = 0;
var errors = false;
//request handler for DynamoDB
function requestHandler(err, data) {
console.log('In the request handler...');
return function (err, data) {
completedRequests++;
errors = errors ? true : err;
//log error
if (errors) {
console.error('Request caused a DB error.');
console.error('ERROR: ' + err);
console.error(JSON.stringify(err, null, 2));
} else {
var res = {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify(data),
isBase64Encoded: false,
};
console.log(`Success: returned ${data}`);
return res;
}
if (completedRequests == batches.length) {
return errors;
}
};
}
//Make request
var params;
for (let j = 0; j < batches.length; j++) {
//items go in params.RequestedItems.id array
//format for the items is {PutRequest : {Item: ITEM_OBJECT}}
params = '{"RequestItems": {"' + tableName + '": []}}';
params = JSON.parse(params);
params.RequestItems[tableName] = batches[j];
console.log('before db.batchWriteItem: ', params);
try {
//send to db
db.batchWrite(params, requestHandler(params));
} catch{
console.error(err)
}
}
Here is the formatted request object and the error:
before db.batchWriteItem:
{ RequestItems:
{ 'some-table-name': [ [Object], [Object], [Object], [Object] ] }
}
In the request handler...
Request caused a DB error.
ERROR: ValidationException: Invalid attribute value type
{
"message": "Invalid attribute value type",
"code": "ValidationException",
"time": "2020-08-04T10:51:13.751Z",
"requestId": "dd49628c-6ee9-4275-9349-6edca29636fd",
"statusCode": 400,
"retryable": false,
"retryDelay": 47.94198279972915
}
You are using the DocumentClient in the nodejs code. This will automatically convert the data format used by DynamoDB to a more easily consumable format.
e.g.
{
"id": {
"S": "A string value"
}
}
would become
{
"id": "A string value"
}
The CLI does not perform this data conversion.
You can use the regular DynamoDB client to not perform this conversion in Nodejs. e.g. const db = new Dynamodb()
I'm trying to log POST, PUT, PATCH json bodies to app insights using ITelemetryInitializer. Every time a post comes in though it seems that my body stream is already disposed of some how. I'm assuming there is something in the request pipeline that I'm not registering/doing properly.
public class RequestBodyLogger : ITelemetryInitializer
{
readonly IHttpContextAccessor httpContextAccessor;
public RequestBodyLogger(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
try
{
if (telemetry is RequestTelemetry requestTelemetry)
{
if ((httpContextAccessor.HttpContext.Request.Method == HttpMethods.Post ||
httpContextAccessor.HttpContext.Request.Method == HttpMethods.Put ||
httpContextAccessor.HttpContext.Request.Method == HttpMethods.Patch) &&
httpContextAccessor.HttpContext.Request.Body.CanRead)
{
const string jsonBody = "JsonBody";
if (requestTelemetry.Properties.ContainsKey(jsonBody))
{
return;
}
//Allows re-usage of the stream
httpContextAccessor.HttpContext.Request.EnableRewind();
var stream = new StreamReader(httpContextAccessor.HttpContext.Request.Body);
var body = stream.ReadToEnd(); <<Blows here object disposed (stream)
//Reset the stream so data is not lost
httpContextAccessor.HttpContext.Request.Body.Position = 0;
requestTelemetry.Properties.Add(jsonBody, body);
}
}
}
catch (Exception e)
{
}
}
}
Configure services method...
public void ConfigureServices(IServiceCollection services)
{
if (_env.IsDevelopment())
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(Constants.ApiVersion, new Info { Title = Constants.ApiName, Version = Constants.ApiVersion });
c.AddSecurityDefinition("Bearer", new ApiKeyScheme { In = "header", Description = "Please enter Bearer Token", Name = "Authorization", Type = "apiKey" });
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> { { "Bearer", Enumerable.Empty<string>() } });
c.IncludeXmlComments($"{AppDomain.CurrentDomain.BaseDirectory}\\TradeJournal.Api.xml");
});
}
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = Configuration["IdentityAuthority"];
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
var json = new WebClient().DownloadString(parameters.ValidIssuer + "/.well-known/jwks.json");
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
return (IEnumerable<SecurityKey>)keys;
},
ValidIssuer = Configuration["IdentityAuthority"],
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateAudience = false
};
});
services
.AddCors(c =>
{
c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
});
services.AddMvc(opts =>
{
opts.Filters.Add(typeof(ModelStateValidationFilter));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(opt =>
{
opt.SerializerSettings.DateFormatString = "yyyy-MM-ddTHH:mm:ssZ";
});
services.AddSingleton<ITelemetryInitializer, RequestBodyLogger>();
services.AddTransient<ExceptionToHttpResponseMiddleware>();
services.AddTransient<MaintenanceMiddleware>();
services.AddRouting(opts =>
{
opts.LowercaseUrls = true;
opts.LowercaseQueryStrings = true;
});
BootstrapLayers(services);
}
```
Configure method...
public void Configure(IApplicationBuilder app)
{
if (_env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"/swagger/{Constants.ApiVersion}/swagger.json", Constants.ApiName);
});
}
else
{
//The default HSTS value is 30 days.You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAuthentication();
app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials());
app.UseMiddleware<ExceptionToHttpResponseMiddleware>();
app.UseMiddleware<MaintenanceMiddleware>();
app.UseHttpsRedirection();
app.UseMvc();
}
**Why is the context disposed by the time AI calls this telemetry initializer?**
This is also discussed in github: https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/940#issuecomment-513297006
Since by the time TelemetryInitializers are run, the request body is disposed, your best bet is to read and populate the body into RequestTelemetry when the body is still available. If its a controller or middleware - then retrieve RequestTelemetry there, add body to it. The following is sample code to be written in controller/middleware where you can retrieve request body.
RequestTelemetry reqTelemetry = httpContext?.Features.Get<RequestTelemetry>();
reqTelemetry.Properties.Add("body","body contents").
I was following an example posted by the async author here but I'm getting an error.
redis-2.2.12
node v0.4.11-pre
Here's my code:
var async = require('async');
var redis = require('redis');
var keys = ['key1', 'key2', 'key3'];
var client = redis.createClient();
var multi = client.multi();
for (var key in keys) {
multi.hmset(key, {'some': 'value'});
}
multi.exec(function(err, res) {
if (err) throw err;
console.dir(res);
var myCallback = function(err, res) {
console.log('in myCallback');
console.dir(res);
client.quit();
process.exit();
};
async.concat(keys, client.hgetall, myCallback);
});
Produces the following output:
$ node redis_test.js
[ 'OK', 'OK', 'OK' ]
node.js:134
throw e; // process.nextTick error, or 'error' event on first tick
^
TypeError: Object #<Object> has no method 'send_command'
at /home/project/node_modules/redis/index.js:666:25
at /home/project/node_modules/async/lib/async.js:508:13
at /home/project/node_modules/async/lib/async.js:97:13
at Array.forEach (native)
at /home/project/node_modules/async/lib/async.js:26:24
at /home/project/node_modules/async/lib/async.js:96:9
at /home/project/node_modules/async/lib/async.js:507:9
at Object.concat (/home/project/node_modules/async/lib/async.js:141:23)
at /home/project/redis_test.js:21:9
at Command.callback (/home/project/node_modules/redis/index.js:827:13)
When async runs client.hgetall, it trashes the value of this inside of hgetall. You can either wrap up an anonymous function to glue this together, or use fn.bind() as shown below.
You also want to avoid using for .. in to iterate over an Array. Use either a regular for loop or arr.forEach(). Your example would have mysteriously failed as written. Here's a version that seems to do what you want:
var async = require('async');
var redis = require('redis');
var keys = ['key1', 'key2', 'key3'];
var client = redis.createClient();
var multi = client.multi();
keys.forEach(function (key) {
multi.hmset(key, {'some': 'value'});
});
multi.exec(function(err, res) {
if (err) throw err;
console.dir(res);
var myCallback = function(err, res) {
console.log('in myCallback');
console.dir(res);
client.quit();
process.exit();
};
async.concat(keys, client.hgetall.bind(client), myCallback);
});
This outputs:
[ 'OK', 'OK', 'OK' ]
in myCallback
[ { some: 'value' },
{ some: 'value' },
{ some: 'value' } ]
To debug the mysterious failure, you can turn on debug logging in node_redis by doing redis.debug_mode = true; before sending any Redis commands.