(Question also asked here.)
I am attempting to connect to and query Tableau via GraphQL using C#. I have been trying for a while now, and keep hitting a wall when sending the query.
I am creating a httpWebRequest to send the query using the following method.
The variables passed in are:
string Url = MyTableauServer/api/metadata/graphql
string Method = "POST"
string payload = """query TestName1 { databaseServersConnection(first: 10) { totalCount, pageInfo{endCursor,hasNextPage,} nodes { connectionType, __typename, hostName, name, port, isEmbedded, tables { id, schema, name, columns { id, name } } } } }"""
The AuthToken is obtained by querying the REST API. The authenticated user has permissions to be able to perform any and all of the required actions.
public static string SendJsonWebRequest(string Url, string Method, string payload, string AuthToken)
{
try
{
string response;
//encode the json payload
byte[] buf = Encoding.UTF8.GetBytes(Uri.EscapeDataString(payload));
//set the system to ignore certificate errors because Tableau server has an invalid cert.
System.Net.ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
//Create the web request and add the json payload
HttpWebRequest wc = WebRequest.CreateHttp(Url) as HttpWebRequest;
wc.Method = Method;
wc.PreAuthenticate = true;
wc.Headers.Add($"X-Tableau-Auth: {AuthToken}, content-type: application/json, accept: application/json");
wc.ContentLength = buf.Length;
wc.GetRequestStream().Write(buf, 0, buf.Length);
try
{
//Send the web request and parse the response into a string
//this is as far as i get
HttpWebResponse wr = wc.GetResponse() as HttpWebResponse;
Stream receiveStream = wr.GetResponseStream();
StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
response = readStream.ReadToEnd();
receiveStream.Close();
readStream.Close();
wr.Close();
}
catch (WebException we)
{
//Catch failed request and return the response code
if (we.Status == WebExceptionStatus.ProtocolError)
{
WebResponse resp = we.Response;
using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
{
response = sr.ReadToEnd();
}
}
else
{
response = ((HttpWebResponse)we.Response).StatusCode.ToString();
}
}
return response;
}
catch (Exception E)
{
logger.Trace($"- ERROR - Error in SendWebRequest. System says: {E.Message}");
return E.Message;
}
}
The most recent error i am getting is "The underlying connection was closed: The connection was closed unexpectedly." being returned at the following line:
HttpWebResponse wr = wc.GetResponse() as HttpWebResponse;
Any ideas or assistance would be greatly appreciated
------------------------------------------------------------
Update:
using Fiddler i get the following response.
{
"timestamp": 1600939439846,
"status": 401,
"error": "Unauthorized",
"message": "User is not authenticated",
"path": "/relationship-service-war/graphql"
}
I am authenticated, Authentication token is valid and is passed in the headers.
------------------------------------------------------
Update:
Adding the code for authentication. logged in user is a server admin so no issues with access rights.
public static string TabLogIn(string User, string Pass, string ContURL = "")
{
string response = "";
try
{
using (XmlWriter loginxml = XmlWriter.Create("signin.xml"))
{
loginxml.WriteStartDocument();
loginxml.WriteStartElement("tsRequest");
loginxml.WriteStartElement("credentials");
loginxml.WriteAttributeString("name", User);
loginxml.WriteAttributeString("password", Pass);
loginxml.WriteStartElement("site");
loginxml.WriteAttributeString("contentUrl", ContURL);
loginxml.WriteEndElement();
loginxml.WriteEndElement();
loginxml.WriteEndElement();
loginxml.WriteEndDocument();
}
XElement myxml = XElement.Load("signin.xml");
string myxmlstring = myxml.ToString();
//send payload to routine to make the web request
string URL = $#"{Enums.Server}/api/{Enums.APIVersion34}/auth/signin";
//Send the above url, the POST method, and the XML Payload string to create the web request
var infotl = SendWebRequest(URL, "POST", myxmlstring);
response = infotl;
File.Delete("signin.xml");
return response;
}
catch(Exception E)
{
logger.Trace($"- ERROR - Error in TabLogIn. System says: {E.Message}");
return response;
}
finally
{
if (File.Exists("signin.xml"))
{
File.Delete("signin.xml");
}
}
}
static string SendWebRequest(string Url, string Method, string payload)
{
try
{
string response;
//encode the XML payload
byte[] buf = Encoding.UTF8.GetBytes(payload);
//set the system to ignore certificate errors because Tableau server has an invalid cert.
System.Net.ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
//Create the web request and add the XML payload
HttpWebRequest wc = WebRequest.Create(Url) as HttpWebRequest;
wc.Method = Method;
wc.ContentType = "text/xml";
wc.ContentLength = buf.Length;
wc.GetRequestStream().Write(buf, 0, buf.Length);
try
{
//Send the web request and parse the response into a string
HttpWebResponse wr = wc.GetResponse() as HttpWebResponse;
Stream receiveStream = wr.GetResponseStream();
StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
response = readStream.ReadToEnd();
receiveStream.Close();
readStream.Close();
wr.Close();
}
catch (WebException we)
{
//Catch failed request and return the response code
response = ((HttpWebResponse)we.Response).StatusCode.ToString();
}
return response;
}
catch(Exception E)
{
logger.Trace($"- ERROR - Error in SendWebRequest. System says: {E.Message}");
return E.Message;
}
}
Double check if you're requesting a token from the correct Tableau Site (if you have multiple sites). Also what permissions does user have and can you share the code where you request the token?
Related
I have a API which returns the json response.
When I call the API from Fiddler it gives me the json reponse
as shown below:
JSON Response:
Call to API from Web page:
protected void FinalCall()
{
// try
// {
string url = txtdomainURL.Text.Trim();
string apiname = txtAPIname.Text.Trim();
string apiURL = apiname+"/"+txtStoreID.Text.Trim()+"/"+txtSerialNo.Text.Trim(); //"duediligence/1/45523354232323424";// 990000552672620";//45523354232323424";
//duediligence/1/45523354232323424 HTTP/1.1
string storeID = txtStoreID.Text.Trim();//Test Store ID:1 Live Store ID: 2
string partnerID = txtPartnerID.Text.Trim();// "1";
string scretKey = txtSecretKey.Text.Trim();// "234623ger787qws3423";
string requestBody = txtRequestBody.Text.Trim(); //"{\"category\": 8}";
string data = scretKey + requestBody;
string signatureHash = SHA1HashStringForUTF8String(data);
lblSignatureHash.Text = signatureHash;
String userName = partnerID;
String passWord = signatureHash;
string credentials = userName + ":" + passWord;//Convert.ToBase64String(Encoding.ASCII.GetBytes(userName + ":" + passWord));
var dataString = JsonConvert.SerializeObject(requestBody); //JsonConvert.SerializeObject(requestBody);
var bytes = Encoding.Default.GetBytes(dataString);
WebClient client = new WebClient();
string base64 = Convert.ToBase64String(Encoding.ASCII.GetBytes(credentials));
client.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(credentials));
lblBase64.Text = base64;
client.Headers.Add(HttpRequestHeader.Accept, "application/json");
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
//string response = cli.UploadString(url + apiURL, dataString); //"{some:\"json data\"}"
string completeURLRequest = url + apiURL;
//I GET ERROR HERE
var result = client.DownloadString(completeURLRequest);
//CODE below this line is not executed
//Context.Response.TrySkipIisCustomErrors = true;
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var jsonObject = serializer.DeserializeObject(result.ToString());
//var result1 = client.UploadData(completeURLRequest, "POST", bytes);
Response.Write(result);
txtwebresponse.Text = jsonObject.ToString();
//}
Now, When the same is executed from a web page it throws exeception '401 Unauthorized Exception'. So, instead of showing error page I want to read the returned JSON error response (as in fiddler) and show to user.
Help Appreciated!
Adapted from this answer to ".Net HttpWebRequest.GetResponse() raises exception when http status code 400 (bad request) is returned" by Jon Skeet:
try
{
using (WebResponse response = request.GetResponse())
{
Console.WriteLine("You will get error, if not do the proper processing");
}
}
catch (WebException e)
{
using (WebResponse response = e.Response)
{
HttpWebResponse httpResponse = (HttpWebResponse) response;
Console.WriteLine("Error code: {0}", httpResponse.StatusCode);
using (Stream data = response.GetResponseStream())
using (var reader = new StreamReader(data))
{
// text is the response body
string text = reader.ReadToEnd();
}
}
}
Slightly changing SilentTremor's answer. Combining the using statements makes the code a little bit shorter.
catch (WebException ex)
{
HttpWebResponse httpResponse = (HttpWebResponse)ex.Response;
using (WebResponse response = ex.Response)
using (Stream data = response.GetResponseStream())
using (StreamReader reader = new StreamReader(data))
{
string errorMessage = reader.ReadToEnd();
}
}
I am trying to access to some rest services of a specific web server for my WP8 app and I can´t do it well. For example, this is the code that I use when I try to login the user. I have to pass a string that represents a Json object ("parameters") with the username and the password and the response will be a json object too. I can't find the way to pass this pasameters in the rest request.
This is the code;
public void login(string user, string passwrd)
{
mLoginData.setUserName(user);
mLoginData.setPasswd(passwrd);
string serviceURL = mBaseURL + "/service/user/login/";
string parameters = "{\"username\":\"" + mLoginData.getUserName() + "\",\"password\":\"" + mLoginData.getPasswd() + "\"}";
//MessageBox.Show(parameters);
//MessageBox.Show(serviceURL);
//build the REST request
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceURL);
request.ContentType = "application/json";
request.Method = "POST";
//async request launchs "Gotresponse(...) when it has finished.
request.BeginGetResponse(new AsyncCallback(GotResponse), request);
}
private void GotResponse(IAsyncResult ar)
{
try
{
string data;
// State of request is asynchronous
HttpWebRequest myHttpWebRequest = (HttpWebRequest)ar.AsyncState;
using (HttpWebResponse response = (HttpWebResponse)myHttpWebRequest.EndGetResponse(ar))
{
// Read the response into a Stream object.
Stream responseStream = response.GetResponseStream();
using (var reader = new StreamReader(responseStream))
{
data = reader.ReadToEnd();
}
responseStream.Close();
}
}
catch (Exception e)
{
string exception = e.ToString();
throw;
}
}
I tried too with the webClient and the httpClient classes too but without any result.
Thanks and sorry for my bad english.
I solved it with the HttpClient class. This is the code.
public async void login(string user, string passwrd)
{
string serviceURL = "";
string parameters = "";
HttpClient restClient = new HttpClient();
restClient.BaseAddress = new Uri(mBaseURL);
restClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, serviceURL);
req.Content = new StringContent(parameters, Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
string responseBodyAsText = "";
try
{
response = await restClient.SendAsync(req);
response.EnsureSuccessStatusCode();
responseBodyAsText = await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException e)
{
string ex = e.Message;
}
if (response.IsSuccessStatusCode==true)
{
dynamic data = JObject.Parse(responseBodyAsText);
}
else
{
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
MessageBox.Show("User or password were incorrect");
}
else
{
MessageBox.Show("NNetwork connection error");
}
}
}
I wasn't setting the header values of the request correctly.
I hope this can help someone.
Could anyone help me with this example of REST api “describe eucalyptus instances” in c# without using AWS sdk for .net?
I give you my sample code. This code is running in aws successfully, but in eucalyptus they give a “404 not found” error.
protected void Page_Load(object sender, EventArgs e)
{
EucaListInstance("xyx/services/Eucalyptus");
}
private void ListEucaInstance(string inboundQueueUrl)
{
// Create a request for the URL.
string date = System.DateTime.UtcNow.ToString("s");
string stringToSign = string.Format("DescribeInstances" + date);
string signature = CalculateEucaSignature(stringToSign, true);
StringBuilder sb = new StringBuilder();
sb.Append(inboundQueueUrl);
sb.Append("?Action=DescribeInstances");
sb.Append("&Version=2013-10-15");
sb.AppendFormat("&AWSAccessKeyId={0}", m_EucaAccessKeyID);
sb.AppendFormat("&Expires={0}", date);
sb.AppendFormat("&Signature={0}", signature);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(sb.ToString());
HttpWebResponse response = null;
Stream dataStream = null;
StreamReader reader = null;
try
{
request.Credentials = CredentialCache.DefaultCredentials;
response = (HttpWebResponse)request.GetResponse();
// Get the stream containing content returned by the server.
dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
reader = new StreamReader(dataStream);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
// Cleanup the streams and the response.
if (reader != null)
reader.Close();
if (dataStream != null)
dataStream.Close();
if (response != null)
response.Close();
}
}
private string CalculateEucaSignature(string data, bool urlEncode)
{
ASCIIEncoding ae = new ASCIIEncoding();
HMACSHA1 signature = new HMACSHA1(ae.GetBytes(m_EucaSecretKey));
string retSignature = Convert.ToBase64String(signature.ComputeHash(ae.GetBytes(data.ToCharArray())));
return urlEncode ? HttpUtility.UrlEncode(retSignature) : retSignature;
}
You would get a 404 error if you are sending the request to the wrong URL. I would verify that you are sending to the correct URL, which would typically be along the lines of:
http://eucalyptus.your.domain.here.example.com:8773/services/Eucalyptus
You can find the URL to use in your deployment by looking in your eucarc file for the EC2_URL value, or by running the "euca-describe-services -T eucalyptus" admin command (in versions up to 4.0, for 4.0 onward you would use "-T compute")
I'm communicating with my server with the following code,
private void Save_Click(object sender, RoutedEventArgs e)
{
var request = HttpWebRequest.Create(url) as HttpWebRequest;
request.Method = "POST";
request.BeginGetResponse(new AsyncCallback(GotResponse), request);
}
private void GotResponse(IAsyncResult asynchronousResult)
{
try
{
string data;
HttpWebRequest myrequest = (HttpWebRequest)asynchronousResult.AsyncState;
using (HttpWebResponse response = (HttpWebResponse)myrequest.EndGetResponse(asynchronousResult))
{
System.IO.Stream responseStream = response.GetResponseStream();
using (var reader = new System.IO.StreamReader(responseStream))
{
data = reader.ReadToEnd();
}
responseStream.Close();
}
this.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(data);
});
}
catch (Exception e)
{
var we = e.InnerException as WebException;
if (we != null)
{
var resp = we.Response as HttpWebResponse;
var code = resp.StatusCode;
this.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("Message :" + we.Message + " Status : " + we.Status);
});
}
else
throw;
}
}
I'm giving date and amount as my input value,it is url encoded. If all my data's are valid then everything works fine. And so my server will give the data as
{
"code":0,
"message":"Success",
"data":{
"date":xxxx,
"amount":123
}
}
But in case if give an invalid value,(For eg: abcd for 'amount'), then my server would reply as
{
"code":2,
"message":"Invalid value passed"
}
In this case, after Executing the line
using (HttpWebResponse response = (HttpWebResponse)myrequest.EndGetResponse(asynchronousResult))
It jumps to catch, and it display
Message:The remote server returned an error:NotFound. Status:UnKnown Error
Required Solution: It should fetch the result as it did in the previous case.
What sholud i do to fix it?
Well presumably the HTTP status code is 404. You're already accessing the response in your error code though - all you need to do is try to parse it as JSON, just as you are in the success case, instead of using we.Message to show an error message. You should probably be ready for the content to either be empty or not include valid JSON though, and only do this on specific status codes that you expect to still return JSON.
I need to make a POST request with an xml data.
String xml = "";
byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
HttpClient.post(url, data, "text/xml")
Then I call the POST function:
public static String post(String url, byte[] data, String contentType){
String body = "";
body = getResponse("POST", url, data, contentType, 80);
return body;
}
Now I call this function to make the request / get the response:
public static String getResponse(String method, String url, byte[] data, String contentType, int serverPort)
{
String result = null;
HttpWebRequest request = sendRequest(method, url, data, contentType, serverPort);
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
if (response != null){
// Get the stream associated with the response
Stream receiveStream = response.GetResponseStream ();
// Pipes the stream to a higher level stream reader
StreamReader readStream = new StreamReader (receiveStream, System.Text.Encoding.UTF8);
result = readStream.ReadToEnd ();
}
}
catch(WebException ex)
{
if (ex.Status == WebExceptionStatus.ProtocolError){
throw new HttpClientException("HTTP response error. ", (int)(((HttpWebResponse)ex.Response).StatusCode), ((HttpWebResponse)ex.Response).StatusDescription);
}
else{
throw new HttpClientException("HTTP response error with status: " + ex.Status.ToString());
}
}
}
and
public static HttpWebRequest sendRequest(String method, String url, byte[] data, String contentType, int serverPort){
HttpWebRequest request = null;
try
{
UriBuilder requestUri = new UriBuilder(url);
requestUri.Port = serverPort;
request = (HttpWebRequest)WebRequest.Create(requestUri.Uri);
request.Method = method;
//
if ((method == "POST") && (data != null) && (data.Length > 0)){
request.ContentLength = data.Length;
request.ContentType = ((String.IsNullOrEmpty(contentType))?"application/x-www-form-urlencoded":contentType);
Stream dataStream = request.GetRequestStream ();
dataStream.Write (data, 0, data.Length);
// Close the Stream object.
dataStream.Close ();
}
}
catch(WebException ex)
{
if (ex.Status == WebExceptionStatus.ProtocolError){
throw new HttpClientException("HTTP request error. ", (int)(((HttpWebResponse)ex.Response).StatusCode), ((HttpWebResponse)ex.Response).StatusDescription);
}
else{
throw new HttpClientException("HTTP request error with status: " + ex.Status.ToString());
}
}
}
It always give me an HttpCliendException:
video_api.HttpClientException: HttpClient exception :HTTP response error. with `code: 400` and `status: Bad Request`
But when I tried it with Firefox addon HTTP Resource Test, it ran fine and get 202 Accepted status with the same XML doc.
I consoled the content-type and data.length before the post request was called, the content-type was text/xml and the data.length is 143.
I have known some websites to be picky about request headers and return different results solely based on those values. Compare the request headers of the HTTP request in FireFox, and your request, and if you mimic the headers in the FireFox Resource Test, it will most likely work (Request.AddHeader("name", "value")). The other difference to note might be the user-agent which again can be picky for web servers.
Use fiddler (http://www.fiddler2.com/fiddler2/) to see exactly what headers are sent by Firefox. Then see what's different in the headers you are sending.
In my project i use some custom settings in the config.area
<defaultProxy useDefaultCredentials="true">
...
</defaultProxy>
It helped me disabling the proxy:
request.Proxy = null;
Another Solution would be that one messed up SSL and NON-SSL Ports.
When you talk plain http to a https port the answer 400 Bad Request on Apache.