Verifying download box pop up using selenium - unit-testing

I have a website where I am using selenium for integration testing.
I have link there that is generated using multiple variables from page.
I would like to verify the download pop up box is displaying if at all possible, when i am simulating click on link to download the file.
I know i can have JsUnit that will do that for me.
Any ideas?

Selenium is lousy when working with downloads. Clicking the link will get you into trouble.
You could, however, make a request using HttpURLConnection, Apache HttpComponents (or maybe just a file get through URL) for the link specified and assert a 200 OK response. Or try to get the file - this is my favourite tool for this with Selenium.

Thnx to Slanec i have took up your examples.
Ok after investigation I have decided that best solution will be
something along this line.
public int GetFileLenghtFromUrlLocation(string location)
{
int len = 0;
int timeoutInSeconds = 5;
// paranoid check for null value
if (string.IsNullOrEmpty(location)) return 0;
// Create a web request to the URL
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(location);
myRequest.Timeout = timeoutInSeconds * 1000;
myRequest.AddRange(1024);
try
{
// Get the web response
HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
// Make sure the response is valid
if (HttpStatusCode.OK == myResponse.StatusCode)
{
// Open the response stream
using (Stream myResponseStream = myResponse.GetResponseStream())
{
if (myResponseStream == null) return 0;
using (StreamReader rdr = new StreamReader(myResponseStream))
{
len = rdr.ReadToEnd().Length;
}
}
}
}
catch (Exception err)
{
throw new Exception("Error saving file from URL:" + err.Message, err);
}
return len;
}

Related

Tracking failed jobs in SAS

I'm looking to improve the way to track a server that uses SAS to collect information from several databases, the challenge here is that it's one of those servers that a company used for a while without any governance, folders are everywhere, no actual folder structure, anyone was doing whatever they pleased with it. You know the drill.
The option that I found for now is to put a macro on the jobs that are failing that will track the logs, and if it finds an error, then it should send out an email to a distribution list.
But this solution isn't a very good software architecture, every time a new job is scheduled in the crontab, I need to slap the macro there, and if the things move to another directory it stops working and all of that, a lot of manual work cascading down.
A solution that I'm looking for would be finding something that can read all logs from a list of directories (and this would be the biggest manual update it needs then, but only this, and only because I don't have authorization to move the folders), fetch the ones that have errors, and output this to a web page, so then we can only check that web page. to know centralized information (when it's scheduled, when it last ran, etc, etc).
By any chance does anyone have any suggestions here on how to do this? I thought of running a SAS script to just output a txt file that I can fetch somewhere and then reading that with Javascript on a web page, but I ran into a few difficulties on how to loop through all of the folders, and because this scripting language doesn't fell really feel all that great for this purpose. But I'm out of ideas for now. Recommendations?
If you are using Node server side to deliver web content, you can write a function that scans the folders listed in a control file for log files containing ERROR: messages and reports them.
For example:
log folders.txt (control file)
d:\temp\logs\job-1
d:\temp\logs\tuesday jobs
d:\jobs\wednesday\job set 1
d:\jobs\wednesday\job set 2\prod
node app (test version)
const express = require('express');
const app = express();
const port = 8081;
app
.get('/sas/logs/errors', (req, res) => {
res.setHeader('Content-Type', 'text/html');
processFolders(res);
res.end();
})
;
app.listen(port, () => console.log(`Listening on port ${port}`));
const fs = require('fs');
const readline = require('readline');
const path = require('path');
const listOfFoldersFile = 'log folders.txt';
function processFolders(res) {
var level1Count = 0;
var level2Count = 0;
var level3Count = 0;
var content;
try {
content = fs.readFileSync(listOfFoldersFile, 'UTF-8');
}
catch (err) {
console.log(err.toString());
res.write('<p>Configuration error in processFolders.</p>');
return;
}
const folders = content.split(/\r?\n/);
for (let folder of folders) {
scanForLogs(folder);
}
if (level1Count == 0) {
res.write('<p>No error messages found in SAS logs.</p>');
return;
}
return;
function scanForLogs(folder) {
//console.log(`scan folder ${folder}`);
var logfiles;
try {
logfiles = fs.readdirSync (folder).filter(filename => filename.match(/\.log$/));
}
catch (err) {
console.log(err.toString());
res.write('<p>Problem scanning a log folder.</p>');
return;
}
level2Count = 0;
for (let logfile of logfiles) {
parseSASLog(folder, logfile);
}
}
function parseSASLog(folder, filename) {
//console.log(`parse log ${path.join(folder,filename)}`);
var content;
try {
content = fs.readFileSync(path.join(folder,filename), 'UTF-8');
}
catch (err) {
console.log(err.toString());
res.write(`<p>Problem reading file ${filename}</p>`);
return;
}
const lines = content.split(/\r?\n/);
level3Count = 0;
var linenum = 0;
for (const line of lines) {
linenum++;
if (line.match (/^ERROR:/)) {
reportErrorMessage(folder, filename, line, linenum);
}
}
if (level3Count) {
res.write('</pre>');
}
}
function reportErrorMessage(folder, filename, line, linenum) {
//console.log(`reportErrorMessage`);
level1Count++;
level2Count++;
level3Count++;
if (level1Count == 1) {
res.write('<h1>SAS errors</h1>');
}
if (level2Count == 1) {
res.write(`<h2>folder: ${folder}<h2>`);
}
if (level3Count ==1) {
res.write(`<h3>file: ${filename}</h3><pre>`);
}
res.write(`${linenum}: ${line}\n`);
}
}
Errors reported (example)
Other approaches could include
asynchronous server side scans delivering output to client side piecewise through signalR
delivering error report as json data which the client side uses to render the report

VS 2015: Running Unit Tests from console, Selenium ChromeDriver problems

I'm 100% new here, and I'm not sure if anything else is expected before I post this question. But I didn't find anything about this exact issue when searching, so here goes:
I have a Solution framework using Selenium ChromeDriver for automating a web site, and an included Unit Test project for running the tests.
The tests can be run fine from the VS Test Explorer. But when I try running them from the VS console with
MSTest.exe /testcontainer:d:\Source\Workspaces\QA\Automation\Tests\bin\Debug\Tests.dll /resultsfile:d:\QA\testresults\autotests\regressiontests\tests.trx
i get these errors in the results file:
Test method Tests.BasicTests.Data.GetSiteData.GetMyElementId threw exception:
System.Exception: File not found
at Bytescout.Spreadsheet.Spreadsheet.LoadFromFile(String FileName, CacheType Cache, String Delimiter, Encoding Encoding, String Password)
at Bytescout.Spreadsheet.Spreadsheet.LoadFromFile(String FileName)
at TestData.Readers.ExcelByteScout.ExcelFindRows(String url, String sheetname, String value, Int32 col) in d:\Source\Workspaces\QA\FlowAutomation\TestData\Readers\EscelByteScout.cs:line 113
at TestData.SiteData.MyElements.GetElementId(String location, String element) in d:\Source\Workspaces\QA\Automation\TestData\SiteData\MyElements.cs:line 27
at Tests.BasicTests.Data.GetSiteData.GetMyElementId() in d:\Source\Workspaces\QA\Automation\Tests\BasicTests\Data\GetSiteData.cs:line 13
This is complaining about a Project -> Class in the solution which reads test data (user logins, CSS elements...) from an excel file.
and
Initialization method Tests.TestTests.OpenTests.OpenMyLabDrop2.Init threw exception. OpenQA.Selenium.DriverServiceNotFoundException: OpenQA.Selenium.DriverServiceNotFoundException: The chromedriver.exe file does not exist in the current directory or in a directory on the PATH environment variable. The driver can be downloaded at http://chromedriver.storage.googleapis.com/index.html..
at OpenQA.Selenium.DriverService.FindDriverServiceExecutable(String executableName, Uri downloadUrl)
at OpenQA.Selenium.Chrome.ChromeDriverService.CreateDefaultService()
at Common.Selenium.GCDriver.Initialize() in d:\Source\Workspaces\QA\FlowAutomation\Common\Selenium\GCDriver.cs:line 19
at FlowTests.Base.MyLabDrop2Test.Init() in d:\Source\Workspaces\QA\FlowAutomation\FlowTests\Base\MyLabDrop2Test.cs:line 15
this complains about a missing ChromeDriver.exe which is definetely not missing.
I'm not COMPLETELY new to VS and Selenium, but this I don't get, since the tests run fine from VS but complain about missing references from the console.
So I'm pretty sure there's something I'm missing when running the tests from the console, but what?
EDIT:
The relevant code from ExceByteScout.cs:
using System;
using System.Collections.Generic;
using Bytescout.Spreadsheet;
namespace TestData.Readers {
public class ExcelByteScout {
public static List<string> ExcelReturnXYZ(string url, string sheetname, string cell1, string cell2, string cell3) {
Spreadsheet document = new Spreadsheet();
document.LoadFromFile(url);
Worksheet worksheet = document.Workbook.Worksheets.ByName(sheetname);
List<string> cellvalues = new List<string>();
string name = worksheet.Cell(cell1).Value.ToString();
string user = worksheet.Cell(cell2).Value.ToString();
string pass = worksheet.Cell(cell3).Value.ToString();
cellvalues.Add(name);
cellvalues.Add(user);
cellvalues.Add(pass);
document.Close();
return cellvalues;
}
public static string[] ExcelFindRows(string url, string sheetname, string value, int col) {
Spreadsheet document = new Spreadsheet();
Worksheet worksheet = null;
List<string> values = new List<string>();
string[] userdata = null;
try {
document.LoadFromFile(url);
}
catch (System.Exception ex) {
System.Diagnostics.Debug.WriteLine(ex.Message);
throw;
}
try {
worksheet = document.Workbook.Worksheets.ByName(sheetname);
}
catch (System.NullReferenceException nrex) {
System.Diagnostics.Debug.WriteLine(nrex.Message);
throw;
}
int i = 0;
while (!worksheet.Cell(i, col).ValueAsString.Equals("")) {
if (worksheet.Cell(i, col).ValueAsString.Equals(value)) {
int rowcellcount = 0;
for (int y = 0; !worksheet.Cell(i, y).ValueAsString.Equals(""); y++) {
rowcellcount++;
}
userdata = new string[rowcellcount];
for (int x = 0; x < rowcellcount; x++) {
userdata[x] = worksheet.Cell(i, x).ValueAsString;
}
break;
}
i++;
}
document.Close();
return userdata
}
UPDATE:
The FileNotFound problem was due to a relative path.
But the ChromeDriver not found issue is a mystery. Specifically because this has worked before, in a different solution but with the exact same way of running the tests from the console.
Also, I'm standing in the /Debug folder of the project when I run MSTest.exe, and chromedriver.exe IS in this folder. I cannot see where else it needs to be?
File not found usually means that the file is not found on the location you expect it to be. This can happen because you run from an other path or the file is not copied.
Check the url before the line document.LoadFromFile(url); by Console.WriteLine(url). Then copy the excel to the correct location or fix the url.

Google Application Credentials set and not found

I have an Amazon EC2 with Linux Instance set up and running for my Java Web Application to consume REST requests. The problem is that I am trying to use Google Cloud Vision in this application to recognize violence/nudity in users pictures.
Accessing the EC2 in my Terminal, I set the GOOGLE_APPLICATION_CREDENTIALS by the following command, which I found in the documentation:
export GOOGLE_APPLICATION_CREDENTIALS=<my_json_path.json>
Here comes my first problem: When I restart my server, and ran 'echo $GOOGLE_APPLICATION_CREDENTIALS' the variable is gone. Ok, I set it to the bash_profile and bashrc and now it is ok.
But, when I ran my application, consuming the above code, to get the adult and violence status of my picture, I got the following error:
java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
My code is the following:
Controller:
if(SafeSearchDetection.isSafe(user.getId())) {
if(UserDB.updateUserProfile(user)==false){
throw new SQLException("Failed to Update");
}
} else {
throw new IOException("Explicit Content");
}
SafeSearchDetection.isSafe(int idUser):
String path = IMAGES_PATH + idUser + ".jpg";
try {
mAdultMedicalViolence = detectSafeSearch(path);
if(mAdultMedicalViolence.get(0) > 3)
return false;
else if(mAdultMedicalViolence.get(1) > 3)
return false;
else if(mAdultMedicalViolence.get(2) > 3)
return false;
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return true;
detectSafeSearch(String path):
List<AnnotateImageRequest> requests = new ArrayList<AnnotateImageRequest>();
ArrayList<Integer> adultMedicalViolence = new ArrayList<Integer>();
ByteString imgBytes = ByteString.readFrom(new FileInputStream(path));
Image img = Image.newBuilder().setContent(imgBytes).build();
Feature feat = Feature.newBuilder().setType(Type.SAFE_SEARCH_DETECTION).build();
AnnotateImageRequest request = AnnotateImageRequest.newBuilder().addFeatures(feat).setImage(img).build();
requests.add(request);
ImageAnnotatorClient client = ImageAnnotatorClient.create();
BatchAnnotateImagesResponse response = client.batchAnnotateImages(requests);
List<AnnotateImageResponse> responses = response.getResponsesList();
for (AnnotateImageResponse res : responses) {
if (res.hasError()) {
System.out.println("Error: "+res.getError().getMessage()+"\n");
return null;
}
SafeSearchAnnotation annotation = res.getSafeSearchAnnotation();
adultMedicalViolence.add(annotation.getAdultValue());
adultMedicalViolence.add(annotation.getMedicalValue());
adultMedicalViolence.add(annotation.getViolenceValue());
}
for(int content : adultMedicalViolence)
System.out.println(content + "\n");
return adultMedicalViolence;
My REST application was built above a Tomcat8. After no success running the command:
System.getenv("GOOGLE_APPLICATION_CREDENTIALS")
I realize that my problem was in the Environment Variables to Tomcat installation. To correct this, I just created a new file setenv.sh in my /bin with the content:
GOOGLE_APPLICATION_CREDENTIALS=<my_json_path.json>
And it worked!

Some Problems of Indy 10 IdHTTP Implementation

In regard to Indy 10 of IdHTTP, many things have been running perfectly, but there are a few things that don't work so well here. That is why, once again, I need your help.
Download button has been running perfectly. I'm using the following code :
void __fastcall TForm1::DownloadClick(TObject *Sender)
{
MyFile = SaveDialog->FileName;
TFileStream* Fist = new TFileStream(MyFile, fmCreate | fmShareDenyNone);
Download->Enabled = false;
Urlz = Edit1->Text;
Url->Caption = Urlz;
try
{
IdHTTP->Get(Edit1->Text, Fist);
IdHTTP->Connected();
IdHTTP->Response->ResponseCode = 200;
IdHTTP->ReadTimeout = 70000;
IdHTTP->ConnectTimeout = 70000;
IdHTTP->ReuseSocket;
Fist->Position = 0;
}
__finally
{
delete Fist;
Form1->Updated();
}
}
However, a "Cancel Resume" button is still can't resume interrupted downloads. Meant, it is always sending back the entire file every time I call Get() though I've used IdHTTP->Request->Ranges property.
I use the following code:
void __fastcall TForm1::CancelResumeClick(TObject *Sender)
{
MyFile = SaveDialog->FileName;;
TFileStream* TFist = new TFileStream(MyFile, fmCreate | fmShareDenyNone);
if (IdHTTP->Connected() == true)
{
IdHTTP->Disconnect();
CancelResume->Caption = "RESUME";
IdHTTP->Response->AcceptRanges = "Bytes";
}
else
{
try {
CancelResume->Caption = "CANCEL";
// IdHTTP->Request->Ranges == "0-100";
// IdHTTP->Request->Range = Format("bytes=%d-",ARRAYOFCONST((TFist->Position)));
IdHTTP->Request->Ranges->Add()->StartPos = TFist->Position;
IdHTTP->Get(Edit1->Text, TFist);
IdHTTP->Request->Referer = Edit1->Text;
IdHTTP->ConnectTimeout = 70000;
IdHTTP->ReadTimeout = 70000;
}
__finally {
delete TFist;
}
}
Meanwhile, by using the FormatBytes function, found here, has been able to shows only the size of download files. But still unable to determine the speed of download or transfer speed.
I'm using the following code:
void __fastcall TForm1::IdHTTPWork(TObject *ASender, TWorkMode AWorkMode, __int64 AWorkCount)
{
__int64 Romeo = 0;
Romeo = IdHTTP->Response->ContentStream->Position;
// Romeo = AWorkCount;
Download->Caption = FormatBytes(Romeo) + " (" + IntToStr(Romeo) + " Bytes)";
ForSpeed->Caption = FormatBytes(Romeo);
ProgressBar->Position = AWorkCount;
ProgressBar->Update();
Form1->Updated();
}
Please advise and give an example. Any help would sure be appreciated!
In your DownloadClick() method:
Calling Connected() is useless, since you don't do anything with the result. Nor is there any guarantee that the connection will remain connected, as the server could send a Connection: close response header. I don't see anything in your code that is asking for HTTP keep-alives. Let TIdHTTP manage the connection for you.
You are forcing the Response->ResponseCode to 200. Don't do that. Respect the response code that the server actually sent. The fact that no exception was raised means the response was successful whether it is 200 or 206.
You are reading the ReuseSocket property value and ignoring it.
There is no need to reset the Fist->Position property to 0 before closing the file.
Now, with that said, your CancelResumeClick() method has many issues.
You are using the fmCreate flag when opening the file. If the file already exists, you will overwrite it from scratch, thus TFist->Position will ALWAYS be 0. Use fmOpenReadWrite instead so an existing file will open as-is. And then you have to seek to the end of the file to provide the correct Position to the Ranges header.
You are relying on the socket's Connected() state to make decisions. DO NOT do that. The connection may be gone after the previous response, or may have timed out and been closed before the new request is made. The file can still be resumed either way. HTTP is stateless. It does not matter if the socket remains open between requests, or is closed in between. Every request is self-contained. Use information provided in the previous response to govern the next request. Not the socket state.
You are modifying the value of the Response->AcceptRanges property, instead of using the value provided by the previous response. The server tells you if the file supports resuming, so you have to remember that value, or query it before then attempting to resumed download.
When you actually call Get(), the server may or may not respect the requested Range, depending on whether the requested file supports byte ranges or not. If the server responds with a response code of 206, the requested range is accepted, and the server sends ONLY the requested bytes, so you need to APPEND them to your existing file. However, if the server response with a response code of 200, the server is sending the entire file from scratch, so you need to REPLACE your existing file with the new bytes. You are not taking that into account.
In your IdHTTPWork() method, in order to calculate the download/transfer speed, you have to keep track of how many bytes are actually being transferred in between each event firing. When the event is fired, save the current AWorkCount and tick count, and then the next time the event is fired, you can compare the new AWorkCount and current ticks to know how much time has elapsed and how many bytes were transferred. From those value, you can calculate the speed, and even the estimated time remaining.
As for your progress bar, you can't use AWorkCount alone to calculate a new position. That only works if you set the progress bar's Max to AWorkCountMax in the OnWorkBegin event, and that value is not always know before a download begins. You need to take into account the size of the file being downloaded, whether it is being downloaded fresh or being resumed, how many bytes are being requested during a resume, etc. So there is lot more work involved in displaying a progress bar for a HTTP download.
Now, to answer your two questions:
How to retrieve and save the download file to a disk by using its original name?
It is provided by the server in the filename parameter of the Content-Disposition header, and/or in the name parameter of the Content-Type header. If neither value is provided by the server, you can use the filename that is in the URL you are requesting. TIdHTTP has a URL property that provides the parsed version of the last requested URL.
However, since you are creating the file locally before sending your download request, you will have to create a local file using a temp filename, and then rename the local file after the download is complete. Otherwise, use TIdHTTP.Head() to determine the real filename (you can also use it to determine if resuming is supported) before creating the local file with that filename, then use TIdHTTP.Get() to download to that local file. Otherwise, download the file to memory using TMemoryStream instead of TFileStream, and then save with the desired filename when complete.
when I click http://get.videolan.org/vlc/2.2.1/win32/vlc-2.2.1-win32.exe then the server will process requests to its actual url. http://mirror.vodien.com/videolan/vlc/2.2.1/win32/vlc-2.2.1-win32.exe. The problem is that IdHTTP will not automatically grab through it.
That is because VideoLan is not using an HTTP redirect to send clients to the real URL (TIdHTTP supports HTTP redirects). VideoLan is using an HTML redirect instead (TIdHTTP does not support HTML redirects). When a webbrowser downloads the first URL, a 5 second countdown timer is displayed before the real download then begins. As such, you will have to manually detect that the server is sending you an HTML page instead of the real file (look at the TIdHTTP.Response.ContentType property for that), parse the HTML to determine the real URL, and then download it. This also means that you cannot download the first URL directly into your target local file, otherwise you will corrupt it, especially during a resume. You have to cache the server's response first, either to a temp file or to memory, so you can analyze it before deciding how to act on it. It also means you have to remember the real URL for resuming, you cannot resume the download using the original countdown URL.
Try something more like the following instead. It does not take into account for everything mentioned above (particularly speed/progress tracking, HTML redirects, etc), but should get you a little closer:
void __fastcall TForm1::DownloadClick(TObject *Sender)
{
Urlz = Edit1->Text;
Url->Caption = Urlz;
IdHTTP->Head(Urlz);
String FileName = IdHTTP->Response->RawHeaders->Params["Content-Disposition"]["filename"];
if (FileName.IsEmpty())
{
FileName = IdHTTP->Response->RawHeaders->Params["Content-Type"]["name"];
if (FileName.IsEmpty())
FileName = IdHTTP->URL->Document;
}
SaveDialog->FileName = FileName;
if (!SaveDialog->Execute()) return;
MyFile = SaveDialog->FileName;
TFileStream* Fist = new TFileStream(MyFile, fmCreate | fmShareDenyWrite);
try
{
try
{
Download->Enabled = false;
Resume->Enabled = false;
IdHTTP->Request->Clear();
//...
IdHTTP->ReadTimeout = 70000;
IdHTTP->ConnectTimeout = 70000;
IdHTTP->Get(Urlz, Fist);
}
__finally
{
delete Fist;
Download->Enabled = true;
Updated();
}
}
catch (const EIdHTTPProtocolException &)
{
DeleteFile(MyFile);
throw;
}
}
void __fastcall TForm1::ResumeClick(TObject *Sender)
{
TFileStream* Fist = new TFileStream(MyFile, fmOpenReadWrite | fmShareDenyWrite);
try
{
Download->Enabled = false;
Resume->Enabled = false;
IdHTTP->Request->Clear();
//...
Fist->Seek(0, soEnd);
IdHTTP->Request->Ranges->Add()->StartPos = Fist->Position;
IdHTTP->Request->Referer = Edit1->Text;
IdHTTP->ConnectTimeout = 70000;
IdHTTP->ReadTimeout = 70000;
IdHTTP->Get(Urlz, Fist);
}
__finally
{
delete Fist;
Download->Enabled = true;
Updated();
}
}
void __fastcall TForm1::IdHTTPHeadersAvailable(TObject*Sender, TIdHeaderList *AHeaders, bool &VContinue)
{
Resume->Enabled = ( ((IdHTTP->Response->ResponseCode == 200) || (IdHTTP->Response->ResponseCode == 206)) && TextIsSame(AHeaders->Values["Accept-Ranges"], "bytes") );
if ((IdHTTP->Response->ContentStream) && (IdHTTP->Request->Ranges->Count > 0) && (IdHTTP->Response->ResponseCode == 200))
IdHTTP->Response->ContentStream->Size = 0;
}
#Romeo:
Also, you can try a following function to determine the real download filename.
I've translated this to C++ based on the RRUZ'function. So far so good, I'm using it on my simple IdHTTP download program, too.
But, this translation result is of course still need value improvement input from Remy Lebeau, RRUZ, or any other master here.
String __fastcall GetRemoteFileName(const String URI)
{
String result;
try
{
TIdHTTP* HTTP = new TIdHTTP(NULL);
try
{
HTTP->Head(URI);
result = HTTP->Response->RawHeaders->Params["Content-Disposition"]["filename"];
if (result.IsEmpty())
{
result = HTTP->Response->RawHeaders->Params["Content-Type"]["name"];
if (result.IsEmpty())
result = HTTP->URL->Document;
}
}
__finally
{
delete HTTP;
}
}
catch(const Exception &ex)
{
ShowMessage(const_cast<Exception&>(ex).ToString());
}
return result;
}

Why does SQL Server CLR procedure hang in GetResponse() call to web service

Environment: C#, .Net 3.5, Sql Server 2005
I have a method that works in a stand-alone C# console application project. It creates an XMLElement from data in the database and uses a private method to send it to a web service on our local network. When run from VS in this test project, it runs in < 5 seconds.
I copied the class into a CLR project, built it, and installed it in SQL Server (WITH PERMISSION_SET = EXTERNAL_ACCESS). The only difference is the SqlContext.Pipe.Send() calls that I added for debugging.
I am testing it by using an EXECUTE command one stored procedure (in the CLR) from an SSMS query window. It never returns. When I stop execution of the call after a minute, the last thing displayed is "Calling GetResponse() using http://servername:53694/odata.svc/Customers/". Any ideas as to why the GetResponse() call doesn't return when executing within SQL Server?
private static string SendPost(XElement entry, SqlString url, SqlString entityName)
{
// Send the HTTP request
string serviceURL = url.ToString() + entityName.ToString() + "/";
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(serviceURL);
request.Method = "POST";
request.Accept = "application/atom+xml,application/xml";
request.ContentType = "application/atom+xml";
request.Timeout = 20000;
request.Proxy = null;
using (var writer = XmlWriter.Create(request.GetRequestStream()))
{
entry.WriteTo(writer);
}
try
{
SqlContext.Pipe.Send("Calling GetResponse() using " + request.RequestUri);
WebResponse response = request.GetResponse();
SqlContext.Pipe.Send("Back from GetResponse()");
/*
string feedData = string.Empty;
Stream stream = response.GetResponseStream();
using (StreamReader streamReader = new StreamReader(stream))
{
feedData = streamReader.ReadToEnd();
}
*/
HttpStatusCode StatusCode = ((HttpWebResponse)response).StatusCode;
response.Close();
if (StatusCode == HttpStatusCode.Created /* 201 */ )
{
return "Created # Location= " + response.Headers["Location"];
}
return "Creation failed; StatusCode=" + StatusCode.ToString();
}
catch (WebException ex)
{
return ex.Message.ToString();
}
finally
{
if (request != null)
request.Abort();
}
}
The problem turned out to be the creation of the request content from the XML. The original:
using (var writer = XmlWriter.Create(request.GetRequestStream()))
{
entry.WriteTo(writer);
}
The working replacement:
using (Stream requestStream = request.GetRequestStream())
{
using (var writer = XmlWriter.Create(requestStream))
{
entry.WriteTo(writer);
}
}
You need to dispose the WebResponse. Otherwise, after a few calls it goes to timeout.
You are asking for trouble doing this in the CLR. And you say you are calling this from a trigger? This belongs in the application tier.
Stuff like this is why when the CLR functionality came out, DBAs were very concerned about how it would be misused.