I am following the example here https://cloud.google.com/run/docs/tutorials/pubsub except I am trying to make it work in C#. I have it working pretty much down to the point where if I publish a message in the topic I created, it calls Cloud Run which in turn calls my api's POST method, however inside that POST method the Body Message is always null. I have tried passing in both text as well as JSON objects while publishing a message. In all cases, the Body Message is always run. Any idea what I am doing wrong here and why is the message always null?
Here is my controller post method.
[HttpPost]
public IActionResult Post([FromBody] Body body)
{
Body.Message message = body.getMessage();
if (message == null)
{
string msg1 = "Bad Request: invalid Pub/Sub message format";
Console.WriteLine(msg1);
return BadRequest(msg1);
}
string data = message.getData();
string target = String.IsNullOrEmpty(data) ? "World" :
Encoding.Unicode.GetString(Convert.FromBase64String(Convert.ToBase64String(Encoding.Unicode.GetBytes(data))));
string msg = "Hello " + target + "!";
Console.WriteLine(msg);
return Ok(msg);
}
Here is my Body.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace cloudrun_pubsub.Models
{
public class Body
{
private Message message;
public Body() { }
public Message getMessage()
{
return message;
}
public void setMessage(Message message)
{
this.message = message;
}
public class Message
{
private String messageId;
private String publishTime;
private String data;
public Message() { }
public Message(String messageId, String publishTime, String data)
{
this.messageId = messageId;
this.publishTime = publishTime;
this.data = data;
}
public String getMessageId()
{
return messageId;
}
public void setMessageId(String messageId)
{
this.messageId = messageId;
}
public String getPublishTime()
{
return publishTime;
}
public void setPublishTime(String publishTime)
{
this.publishTime = publishTime;
}
public String getData()
{
return data;
}
public void setData(String data)
{
this.data = data;
}
}
public override string ToString()
{
return "Message Data: " + message.getData() + " "
+ "Message Id: " + message.getMessageId() + " "
+ "Message Publish Time: " + message.getPublishTime() + "." ;
}
}
}
And I have tried publishing messages in the following two ways
Test data.
and
{
"message": {
"messageId": "Message Id",
"publishTime": "123",
"data": "Test data."
}
}
This is what I see in StackDriver Logs.
{
insertId: "5dfa6ecf0001087146655f09"
labels: {
instanceId: "00bf4bf02d946dcf9d9f207e05a85bd978c008d75391808a08560c252798d9b965310e1d2e0f43407b7bfa1d03e46837a64159f2afe2623f90b817"
}
logName: "projects/codelabs-123456/logs/run.googleapis.com%2Fstdout"
receiveTimestamp: "2019-12-18T18:24:15.317529704Z"
resource: {
labels: {
configuration_name: "pubsub-tutorial"
location: "us-central1"
project_id: "codelabs-123345"
revision_name: "pubsub-tutorial-00004-rar"
service_name: "pubsub-tutorial"
}
type: "cloud_run_revision"
}
textPayload: "Bad Request: invalid Pub/Sub message format"
timestamp: "2019-12-18T18:24:15.067697Z"
}
and
{
httpRequest: {
latency: "0.069122189s"
protocol: "HTTP/1.1"
remoteIp: "35.187.139.4"
requestMethod: "POST"
requestSize: "1646"
requestUrl: "https://pubsub-tutorial-12312312-uc.a.run.app/"
responseSize: "280"
status: 400
userAgent: "APIs-Google; (+https://developers.google.com/webmasters/APIs-Google.html)"
}
insertId: "5dfa6ecf0001111dc2b15123"
labels: {
instanceId: "00bf4bf02d946dcf9d9f207e05a85bd978cd75391808a08560c252798d9b965310eb71d2e0f43407b7bfa1d03e46837a64159f2afe2623f90b817"
}
logName: "projects/codelabs-123345/logs/run.googleapis.com%2Frequests"
receiveTimestamp: "2019-12-18T18:24:15.210641703Z"
resource: {
labels: {
configuration_name: "pubsub-tutorial"
location: "us-central1"
project_id: "codelabs-123456"
revision_name: "pubsub-tutorial-00004-rar"
service_name: "pubsub-tutorial"
}
type: "cloud_run_revision"
}
severity: "WARNING"
timestamp: "2019-12-18T18:24:15.069917Z"
trace: "projects/codelabs-123456/traces/8bb4f905bfc568a64bffa978a3ae1617"
}
The problem was in the Body class. I changed it to the following and it worked.
public class PostMessage
{
public PubsubMessage message { get; set; }
public string subscription { get; set; }
}
/// <summary>
/// Pubsub's inner message.
/// </summary>
public class PubsubMessage
{
public string data { get; set; }
public string messageId { get; set; }
public Dictionary<string, string> attributes { get; set; }
}
Related
I am trying to post data from a react application to a wcf service class. Its a simple object with just name age and gender. The service is being called from the react app but with no data. If I use get request and parameter passing the data is passing to the service but I want to use post request. Is it necessary to match the field names as in the wcf service class.
For testing purpose in react component I am using this code
const headers = {
'content-type': 'application/json'
}
const puser = {
Name:'Test',
Age:'23',
Gender:'F',
}
axios.post('http://localhost:3292/myService.svc/adduser',{headers:headers}, puser)
.then(res=> {
console.log(res.data)
})
in wcf service the class code is
public class UserClass
{
string name;
int age;
string gender;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public string Gender
{
get
{
return gender;
}
set
{
gender = value;
}
}
}
and in service call
public string AddUser(List<User> U)
{
UserClass usr = new UserClass();
return "success";
}
Axios post method expects data as second argument. Try to swap puser with {headers}.
https://github.com/axios/axios#axiosposturl-data-config
Do not add header in axios.post, code should look like this:
const puser = {
Name:'Test',
Age:'23',
Gender:'F',
}
axios.post('http://localhost:8000/AddUser', puser)
.then(res=> {
console.log(res.data)
})
Feel free to let me know if the problem persists.
UPDATE:
Set header in Postman:
Axios.post can also set the header like the following:
axios.post('http://localhost:8000/AddUser',{ Name:'Test',
Age:'2311',
Gender:'FF'}, {headers: {
'content-type': 'application/json'
}})
.then(res=> {
console.log(res.data)
})
Here is my project:
Program.cs:
using Demo_rest_ConsoleApp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp71
{
public class User
{
string name;
int age;
string gender;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public string Gender
{
get
{
return gender;
}
set
{
gender = value;
}
}
}
[ServiceContract]
[CustContractBehavior]
public interface IService
{
[OperationContract]
[WebGet]
string EchoWithGet(string s);
[OperationContract]
[WebInvoke]
string EchoWithPost(string s);
[OperationContract]
[WebInvoke(ResponseFormat =WebMessageFormat.Json,RequestFormat =WebMessageFormat.Json)]
string AddUser(User U);
}
public class Service : IService
{
public string AddUser(User U)
{
Console.WriteLine(U.Age);
Console.WriteLine(U.Gender);
Console.WriteLine(U.Name);
return "success";
}
public string EchoWithGet(string s)
{
return "You said " + s;
}
public string EchoWithPost(string s)
{
return "You said " + s;
}
}
class Program
{
static void Main(string[] args)
{
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri("http://localhost:8000/"));
ServiceEndpoint ep = host.AddServiceEndpoint(typeof(IService), new WebHttpBinding(), "");
ep.EndpointBehaviors.Add(new WebHttpBehavior() { HelpEnabled=true});
host.Open();
Console.WriteLine("Open");
Console.ReadLine();
}
}
}
Soap.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Web;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
namespace Demo_rest_ConsoleApp
{
public class ServerMessageLogger : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
Console.WriteLine(request);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
WebOperationContext ctx = WebOperationContext.Current;
ctx.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
if (ctx.IncomingRequest.Method == "OPTIONS")
{
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
ctx.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,POST,DELETE,OPTIONS");
ctx.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
}
}
}
public class ClientMessageLogger : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return null;
}
}
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = false)]
public class CustContractBehaviorAttribute : Attribute, IContractBehavior, IContractBehaviorAttribute
{
public Type TargetContract => throw new NotImplementedException();
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessageLogger());
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.MessageInspectors.Add(new ServerMessageLogger());
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
return;
}
}
}
I'm trying to send an object to the backend with an $http post, but one of the parameters is always null. I'm formatting the dto in the same way when saving a new object and that works fine, but when I try to call the update function it's not working. What am I missing?
This is my controller code:
vm.postUpdateITSM = function (itsm) {
$http({
method: "POST",
url: "api/sources/" + itsm.Id,
data: {
id: itsm.Id,
dto: {
ConnectorType: itsm.Type,
SourceName: itsm.ServerName,
DisplayName: itsm.DisplayName,
Credentials: JSON.stringify(itsm.UserName,
itsm.Password),
Url: itsm.URL,
Settings: JSON.stringify(itsm.ResolveAlerts ? itsm.ResolveAlerts : false,
itsm.AcknowledgeAlerts ? itsm.AcknowledgeAlerts : false,
itsm.SyncInterval,
itsm.IncidentInterval,
itsm.Status ? itsm.Status : "")
}
}
});
}
And on the back end: The dto is always null when called.
public async Task<IHttpActionResult> Update(int id, [FromBody] SourceDto dto)
{
var source = Mapper.Map<Source>(dto);
source.SourceID = id;
source.ServerCount = "";
var res = await SystemActors.SourceManager.Ask(new UpdateSource(source));
var failure = res as Status.Failure;
if (failure != null)
{
return InternalServerError();
}
var success = ((SqlResult<object>) res).Success;
if (!success)
{
return Content(HttpStatusCode.BadRequest, "Failed to update source.");
}
return Ok(new ResponsePackage {Success = true});
}
And this is the SourceDto class:
public class SourceDto
{
public string ConnectorType { get; set; }
public string SourceName { get; set; }
public string DisplayName { get; set; }
public string Credentials { get; set; }
public string Url { get; set; }
public string Settings { get; set; }
}
Your frontend data is formatted a bit wrong - the data parameter should just be the one object your ASP.NET controller is expecting in the [FromBody], your SourceDto model - and the id should be a query string:
method: "POST",
url: "api/sources/" + itsm.Id,
data: {
ConnectorType: itsm.Type,
SourceName: itsm.ServerName,
DisplayName: itsm.DisplayName,
Credentials: JSON.stringify(itsm.UserName,
itsm.Password),
Url: itsm.URL,
Settings: JSON.stringify(itsm.ResolveAlerts ? itsm.ResolveAlerts : false,
itsm.AcknowledgeAlerts ? itsm.AcknowledgeAlerts : false,
itsm.SyncInterval,
itsm.IncidentInterval,
itsm.Status ? itsm.Status : "")
}
});
ASP.NET will apply the request body to the expected model - if it doesn't match, you'll get null
I have searched through all the tutorials and did exactly as explained there, but I can't reach my controller.
Here is my websocket xml config:
<websocket:handlers>
<websocket:mapping path="/updateHandler" handler="updateHandler"/>
<websocket:sockjs/>
</websocket:handlers>
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/update">
<websocket:sockjs/>
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic"/>
</websocket:message-broker>
I don't actually know do I need the handler, but without it stomp connection fails with "whoops! Lost connection to undefined".
Any suggestion in this direction is also welcome.
Here is my empty handler:
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
public class UpdateHandler extends TextWebSocketHandler {
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
}
}
And my controller
#RestController
public class WebSocketController {
#MessageMapping("/update")
#SendTo("/topic/messages")
public OutputMessage sendmMessage(Message message) {
return new OutputMessage(message, new Date());
}
}
I am using ngStomp from angular as suggested:
var message = {message: 'message body', id: 1};
$scope.testWebSocket = function () {
$stomp.setDebug(function (args) {
console.log(args + '\n');
});
$stomp.connect('/myAppContext/update', {})
.then(function (frame) {
var connected = true;
var subscription = $stomp.subscribe('/topic/messages', function (payload, headers, res) {
$scope.payload = payload;
}, {});
$stomp.send('/myAppContext/app/update', message);
subscription.unsubscribe();
$stomp.disconnect(function () {
console.error('disconnected');
});
}, function(error){
console.error(error);
});
};
My Message Class:
public class Message {
private String message;
private int id;
public Message() {
}
public Message(int id, String text) {
this.id = id;
this.message = text;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
My OutputMessage class:
public class OutputMessage extends Message {
private Date time;
public OutputMessage(Message original, Date time) {
super(original.getId(), original.getMessage());
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
when I execute the testWebSocket() I get following output:
Opening Web Socket...
Web Socket Opened...
>>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000
<<< CONNECTED
version:1.1
heart-beat:0,0
user-name:user#domain.com
connected to server undefined
>>> SUBSCRIBE
id:sub-0
destination:/topic/messages
>>> SEND
destination:/myAppContext/app/update
content-length:33
{"message":"message body","id":1}
Why connected to server undefined?
And why my controller never gets executed after sending a message?
I am using spring-4.1.4 with security-core-3.2.5 and Tomcat server 8.0.18
As a non-pretty workaround, I moved websocket configuration to the Java config and it works.
Config below:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/update")
.withSockJS();
}
I actually don't know why.
I am using the RequiresClaims mechanism in Nancy like this:
public class HomeModule : NancyModule
{
public HomeModule()
{
Get["/"] = ctx => "Go here";
Get["/admin"] = ctx =>
{
this.RequiresClaims(new[] { "boss" }); // this
return "Hello!";
};
Get["/login"] = ctx => "<form action=\"/login\" method=\"post\">" +
"<button type=\"submit\">login</button>" +
"</form>";
Post["/login"] = ctx =>
{
return this.Login(Guid.Parse("332651DD-A046-4489-B31F-B6FA1FB290F0"));
};
}
}
The problem is if the user is not allowed to enter /admin because the user doesn't have claim boss, Nancy just responds with http status 403 and blank body.
This is exactly what I need for the web service part of my application, but there are also parts of my application where nancy should construct page for user. How can I show something more informative to the user?
This is the user mapper that I use:
public class MyUserMapper : IUserMapper
{
public class MyUserIdentity : Nancy.Security.IUserIdentity
{
public IEnumerable<string> Claims { get; set; }
public string UserName { get; set; }
}
public Nancy.Security.IUserIdentity GetUserFromIdentifier(Guid identifier, Nancy.NancyContext context)
{
return new MyUserIdentity { UserName = "joe", Claims = new[] { "peon" } };
}
}
And this is the bootstrapper that I use:
public class MyNancyBootstrapper : DefaultNancyBootstrapper
{
protected override void RequestStartup(
Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines, NancyContext context)
{
base.RequestStartup(container, pipelines, context);
var formAuthConfig = new Nancy.Authentication.Forms.FormsAuthenticationConfiguration
{
RedirectUrl = "~/login",
UserMapper = container.Resolve<Nancy.Authentication.Forms.IUserMapper>()
};
Nancy.Authentication.Forms.FormsAuthentication.Enable(pipelines, formAuthConfig);
}
}
You need to handle the 403 status code as part of the pipeline and then return an html response to the user. Take a look at http://paulstovell.com/blog/consistent-error-handling-with-nancy
I have a WPF application. I am making REST calls from that.
I would like to alter the response XML/JSON of the rest service.
I am using FiddlerCore to intercept the call.
I need to listen to ALL the ports in my local machine.
List<Fiddler.Session> oAllSessions = new List<Fiddler.Session>();
FiddlerCoreStartupFlags oFCSF = FiddlerCoreStartupFlags.Default;
oFCSF = (oFCSF & ~FiddlerCoreStartupFlags.DecryptSSL);
//Fiddler.FiddlerApplication.Startup(8080, true, true);
FiddlerApplication.BeforeRequest += delegate(Fiddler.Session oS)
{
};
FiddlerApplication.BeforeResponse += delegate(Fiddler.Session oS)
{
}
};
Fiddler.FiddlerApplication.Startup(0, true, false);
This issue is resolved - Look at the below link
https://gist.githubusercontent.com/timiles/4079321/raw/268f71249f381649a06f4b48ebfb54cbaa8ee282/MockWebProxyHelper.cs
using System;
using System.Net;
// http://www.fiddler2.com/fiddler/Core/
using Fiddler;
public static class MockWebProxyHelper
{
public enum HttpMethods
{
GET, POST, PUT, Unknown
}
public class Response
{
public Response(string header = "HTTP/1.1 200 OK", string body = "", string contentType = "application/json")
{
Header = header;
Body = body;
ContentType = contentType;
}
public string Header { get; private set; }
public string Body { get; private set; }
public string ContentType { get; private set; }
}
public static Func<HttpMethods, string, Response> GetMockResponse = delegate { return new Response(); };
public static Func<HttpMethods, string, bool> InterceptRequest = delegate { return true; };
public static void SetUp(bool registerAsSystemProxy = false)
{
const int port = 18833;
FiddlerApplication.Startup(port, FiddlerCoreStartupFlags.DecryptSSL
| (registerAsSystemProxy ? FiddlerCoreStartupFlags.RegisterAsSystemProxy : FiddlerCoreStartupFlags.None));
WebRequest.DefaultWebProxy = new WebProxy("localhost", port);
FiddlerApplication.BeforeRequest += BeforeRequest;
}
private static void BeforeRequest(Session session)
{
var httpMethod = GetHttpMethod(session);
var url = session.url;
if (InterceptRequest(httpMethod, url))
{
session.utilCreateResponseAndBypassServer();
var response = GetMockResponse(httpMethod, url);
session.oResponse.headers = Parser.ParseResponse(response.Header);
session.oResponse.headers.Add("Content-Type", response.ContentType);
session.utilSetResponseBody(response.Body);
}
}
private static HttpMethods GetHttpMethod(Session session)
{
return session.HTTPMethodIs("GET") ? HttpMethods.GET
: session.HTTPMethodIs("POST") ? HttpMethods.POST
: session.HTTPMethodIs("PUT") ? HttpMethods.PUT : HttpMethods.Unknown;
}
public static void TearDown()
{
FiddlerApplication.BeforeRequest -= BeforeRequest;
FiddlerApplication.oProxy.Detach();
FiddlerApplication.Shutdown();
}
}