Get requests from 1C are doubled. Handling http request redirection

When developing a procedure for sending information from 1C to the site with platform version 8.3.9.2170, I encountered a problem: the site developer gave me the ability to record necessary information only with the help HTTP request using the PUT method.

Without thinking twice, I sketched out a simple code:

Connection = New HTTPConnection("www.mysite.ru"); Headers = New Match; Headers["Content-Type"] = "application/x-www-form-urlencoded"; Request = New HTTPRequest("/api/order_items/93076?order_item=30", Headers); Connection.Write(Request);

Based on the results of execution, the quantity of goods received at the warehouse should have been entered in the corresponding line of the buyer’s order on the website.

However, as you probably already understood, nothing happened. After I made sure that there were no errors on the site (by sending a similar request through a Chrome plugin), I launched it on my local computer web server and began to experiment.

A strange thing immediately became clear: The above code does not generate a PUT, but a HEAD request!

In the Apache logs I saw the following:

127.0.0.1 - - "HEAD /api/order_items/93076?order_item=30 HTTP/1.1"

I was a little surprised (after all, the manual said PUT in black and white), but I wasn’t confused - you can call the method directly:

Connection.CallHTTPMethod("PUT",Request);

The logs show the same thing:

127.0.0.1 - - "HEAD /api/order_items/93076?order_item=30 HTTP/1.1"

"Maybe I'm doing something wrong?" - I asked myself a question. But there were no hints on the Internet or in the manuals. Well, no one has yet canceled the method of scientific poking. To begin with, I tried doing this:

Connection.CallHTTPMethod("fyvfyv",Request);

In the logs I received:

127.0.0.1 - - "?????? /api/order_items/93076?order_item=30 HTTP/1.1"

Curiously, it means that 1C replaces the PUT method specifically (why did 1C not like it?).

After a few more tries I came up with this:

Connection.CallHTTPMethod("PUT",Request);

In the logs I received:

127.0.0.1 - - "PUT /api/order_items/93076?order_item=30 HTTP/1.1"

And this option has already worked on the site and everyone was happy.

He suggested a more correct solution to the problem: you need to specify a request body, any body, even empty. For example, this option will work:

Connection = New HTTPConnection("www.mysite.ru"); Headers = New Match; Headers["Content-Type"] = "application/x-www-form-urlencoded"; Request = New HTTPRequest("/api/order_items/93076?order_item=30", Headers); Request.SetBodyFromString("",TextEncoding.UTF8, UseByteOrderMark.NotUse); Connection.Write(Request);

And it’s probably quite correct to pass the parameter values ​​themselves in the body of the request.

The conclusion is as follows: the 1C platform considers a PUT request without a body to be erroneous and replaces the method with HEAD.

It is curious that 1C does not track a POST request without a body and does not turn it into a GET, I checked it for fun.

As the well-known Vovochka would say from the famous joke: “Where is the logic?”

I hope my publication will save someone several hours of their life in search of an answer. =)))

In the 1C Enterprise 8.3 platform it became possible to create HTTP services

Using the built-in language, you can now generate a response to a request. At the same time, you have convenient access to the body, headers and source line request, and it is also possible to generate code, body and headers answer at your own discretion.

Compared to the web services available in the SOAP platform, HTTP services have a number of advantages:

  • Ease of programming the client of such services;
  • Potentially less data transferred;
  • Potentially lower computational load;
  • HTTP services are "resource" oriented, while SOAP services are "action" oriented.

The database demonstrates the implementation of http services

http-service List of Invoices

The http service uses URL templates and implements property processing ParametersURL object HTTPServiceRequest
IN in this example shows how you can create a spacer base between the working base and the corporate website
The connection is made to the demo database “ Trade management 11“, in which first for the directory “Counterparties” you need to set an additional property with the name Web password , where we will store the access password.
The following URL parameters will be sent to the http request: TIN as login and password.
When processing a request, a connection is made via ComConnector to the UT (brakes are guaranteed), and sampling is made from there
I don’t pretend that this solution is fast or safe, it’s just an example

So. A new branch has appeared in the metadata tree - HTTP services
We create new service, indicate its name and root URL(list)
The root URL will be used to call our http service
Next, add a URL Template to the http service, specifying “/(Login)/(Password)” as the template.
This template will allow you to receive the corresponding structure of parameters and their values ​​in the URL parameters when processing an http request.
Now, to our URL template we will add a method called “get”, select GET as the http method
Open the handler and write the code
A method handler is a function that must return a value of type HTTPServiceResponse

http-service Order Status

The example implements the processing of variables transferred by the POST method and the generation of a response in the form of an HTML page.
This time the data is sampled from the database where the hs are located, so it works significantly faster than the previous http service.
When implementing a working system, it makes sense to create objects in the database (with a service) and configure data migration from the source database (for example, to separate background process). When processing an http request, sampling is carried out directly from the database where it is located.

Publication

There is plenty of information about installing and configuring a web server.
I used the build httpd-2.2.25-win32-x86-openssl-0.9.8y.exe from here
Installed using the “Next-Next-Finish” method :)
The publication of http services is located in the same place where the publication of web services was and is and is not particularly different.
After installing the web server in the “Configurator” mode, go to the menu “Administration” – “Publishing on a web server”
On the “HTTP services” tab we set the publication name, web server, publication directory, and mark our services (in my case the name is “web”, Apache web server 2.2)
When publishing, the corresponding blocks are automatically written to the httpd.conf configuration file and the server is restarted (at first publication)
Calling the http service
Example: http://mysite/web/hs/list, Where
mysite– server address (if the web server is installed locally, you can use 127.0.0.1)
web– name specified during publication (alias)
hs– a mandatory path segment that tells the server that work will occur with http services
list– root url of the web service

Testing

List of invoices

http://127.0.0.1/web/hs/list/7705260681/pswMP (Don't forget to set up an additional property in the UT for the password)

It is assumed that to access the register of documents, the user uses a direct link containing an INN and password

Order status

http://127.0.0.1/web/hs/check

The request and response are located at the same URL. When entering the page, the GET method is triggered, returning an html form

When you click “Check”, the order number is sent using the POST method to the same URL, the response is returned with the same request form, supplemented with data on the requested order.

In the attached file is the download of the database for 1C 8.3. The configuration includes 2 http services (from the publication), setting up a com connection with the demo database UT 11, and the “Order” document.

What you need to launch and test

  • web server
  • any web browser
  • current release1C: Enterprise 8.3

Well, here's an example of processing an XML file with counterparties on the server side:

VBS code
require_once dirname(__FILE__) . "/../inc/initf.php" ;
class Onec_Import_Customers (
private static $instance ;
function __construct() (

Self::$instance = $this ;

Public function process() (
$rawHttp = file_get_contents("php://input");
$xml_raw = str_replace("xml=", "", $rawHttp) ;

If ($_SESSION["loggedin"] || true)(
file_put_contents("log/onec_import_customers_" .time(). ".log", "REQUEST" . print_r($_REQUEST, 1) . " SERVER " . print_r($_SERVER,1). " FILES " . print_r($_FILES, 1) .$xml_raw) ;
file_put_contents("log/onec_import_customers_last.log", "REQUEST" . print_r($_REQUEST, 1) . " SERVER " . print_r($_SERVER,1). " FILES " . print_r($_FILES,1) . $xml_raw) ;

//$xml = stripslashes($_POST["xml"]);
$xml = stripslashes($xml_raw);
if(!$xml) (
$xml = $xml_raw ;
//die ("no XML data (post key "xml")") ;
}
if ($this->setCustomers($xml)) (
die("OK");
) else (
die("FAIL");
}
) else (
die();
}
}

Private function setCustomers($xml)(
$db = db::getInstance() ;

$sxml = simplexml_load_string($xml) ;

$customers = $sxml->("Договор”) ? $sxml->("РІРѕРіРѕРІРѕСЂ") : self::err("Invalid file format. Customers.") ;

$final = array () ;
$k = 0 ;

$allCustomers = array () ;

Foreach ($customers as $cust) (
$password = base::generatePassword(6,1) ;

$arr ["password"] = $password ;

$arr ["email"] = (array)$cust->("Почта") ;//? (array)$cust->("Почта") : self::err("Invalid file format. Customer no:" . $k . ". Invalid email") ;
$arr ["email"] = $arr ["email"] ? $arr ["email"] : "";//: self::err("Invalid file format. customer no:" . $k . ". Invalid email" ;

$arr ["app_name"] = (array)$cust->("РќР°Ременование") ;//? (array)$cust->("РќР°Ременование") : self::err("Invalid file format. Customer no:" . $k . ". Invalid name" ;
$arr ["app_name"] = $arr ["app_name"] ? $arr ["app_name"] : "";//self::err("Invalid file format. customer no:" . $k . ". Invalid name") ;

$arr ["clientid"] = (array)$cust->("Номер") ? (array)$cust->("Номер") : self::err("Invalid file format. Customers no:" . $k . ". Invalid clientid" ;
$arr ["clientid"] = $arr ["clientid"] ? $arr ["clientid"] : self::err("Invalid file format. Customers no:" . $k . ". Invalid clientid" ;

$arr ["date"] = (array)$cust->("Дата”) ? (array)$cust->("Дата") : self::err("Invalid file format. Customers no:" . $k." ". Invalid date" ;
$arr ["date"] = $arr ["date"] ? $arr ["date"] : self::err("Invalid file format. Customers no:" . $k . ". Invalid date" ;

$arr ["date"] = explode(".",$arr ["date"]);
krsort($arr ["date"]);
$arr ["date"] = implode("-",$arr ["date"]) . "00:00:00" ;

$arr ["phone_home"] = (array)$cust->("ТелефоРS") ;//? (array)$cust->("ТелефоРС") : self::err("Invalid file format. Customers no:" . $k . ". Invalid phone") ;
$arr ["phone_home"] = $arr ["phone_home"] ? $arr ["phone_home"] : "";//self::err("Invalid file format. Customers no:" . $k . ". Invalid phone" ;

$arr ["district"] = (array)$cust->("айоРS") ;//? (array)$cust->("айон") : self::err("Invalid file format. Customers no:" . $k . ". Invalid district" ;
$arr ["district"] = $arr ["district"] ? $arr ["district"] : "";//self::err("Invalid file format. Customers no:" . $k . ". Invalid district" ;

$arr ["street"] = (array)$cust->("Улица") ;//? (array)$cust->("Улица") : self::err("Invalid file format. Customers no:" . $k . ". Invalid street") ;
$arr ["street"] = $arr ["street"] ? $arr ["street"] : "";//self::err("Invalid file format. Customers no:" . $k . ". Invalid street" ;

$arr ["building"] = (array)$cust->("Дом”) ;//? (array)$cust->("Дом") : self::err("Invalid file format. Customers no:" . $k . ". Invalid building") ;
$arr ["building"] = $arr ["building"] ? $arr ["building"] : "" ;//self::err("Invalid file format. Customers no:" . $k . ". Invalid building" ;

$arr ["apartament"] = (array)$cust->("Квартира") ;//? (array)$cust->("Квартира") : self::err("Invalid file format. Customers no:" . $k . ". Invalid apartament") ;
$arr ["apartment"] = $arr ["apartment"] ? $arr ["apartament"] : "";// self::err("Invalid file format. Customers no:" . $k . ". Invalid apartament" ;

$allCustomers [$arr ["clientid"]]= array("password"=>$password, "email"=>$arr ["email"]) ;

$final = $arr ;
+$k ;
}

Return $this->buildCustomers($final) ;
/*
if($this->buildCustomers($final)) (
foreach ($allCustomers as $clientid=>$data) (
self::sendPasswordToMail($data["email"], $clientid, $data["password"]) ;
}
}*/

Private static function sendPasswordToMail($email, $client_id, $password) (
$db = db::getInstance() ;
$config = config_model::getInstance() ;
$lng = Request::$currentLang["id"] ;
$email_text = $db->getRow("s1_text", "*", "`alias`="registration_ok" AND `lng_id`="($lng)"");
$body = str_replace("%password%", $password, $email_text["content"]) ;
$body = str_replace("%client_id%", $client_id, $body) ;
base::mailSend($body, $email_text["title"] . " - " . $config->defaultTitle("site.ru") , $email, $app["app_name"], $config->site_admin_mail( " [email protected]"), $config->from_name("site")) ;

Private function buildCustomers ($data) (

$db = db::getInstance() ;

$qry = "I_nsert INTO s1_customer (`active`,`password`,`app_name`,`email`, `date`, `clientid`, `phone_home`, `street`, `district`, `building`, `apartment `) VALUES " ;
foreach ($data as $rows)(
$queryArr = "(
"0"
,MD5("($rows["password">")
,"($db->escape($rows["app_name"]))"
,"($db->escape($rows["email"]))"
,"($db->escape($rows["date"]))"
,"($db->escape($rows["clientid"]))"
,"($db->escape($rows["phone_home"]))"
,"($db->escape($rows["street"]))"
,"($db->escape($rows["district"]))"
,"($db->escape($rows["building"]))"
,"($db->escape($rows["apartment"]))"
)" ;
}
$qry .= implode(",", $queryArr) ;
$qry .= " ON DUPLICATE KEY UPDATE
`app_name` = VALUES(app_name)
,`date` = VALUES(date)
,`email` = VALUES(email)
,`phone_home` = VALUES(phone_home)
,`street` = VALUES(street)
,`district` = VALUES(district)
,`building` = VALUES(building)
,`apartment` = VALUES(apartment)
" ;
return $db->query($qry) ;
}

Public static function getInstance())(
if (!self::$instance)
{
new self() ;
}
return self::$instance ;

Private static function err($msg) (
throw new ImportException($msg) ;
}

Class ImportException extends Exception (

Function __construct ($msg) (
die ("Error: " . $msg) ;

Starting from the second version 8 of the platform, users and developers have the opportunity to use http request directly in 1C. The program supports two types of queries:

  • POST requests;
  • GET requests.

Thus, a fairly convenient tool was created for exchanging data and interacting with web services and services operating via http.

GET request

Of course, the simplest examples of using queries illustrate their capabilities much better than many lines of description. So let's try:

  1. Let's get the body of the main page of our site;
  2. We will work on redirecting the request;
  3. Let's take the picture from the site.

Getting the site body

Let's start with something simple. In Fig..

The result of executing this section of code is a fairly large text, the final section of which is shown in Fig. 2.

Fig.2

In the first line of code we create a connection object to the http resource. An object can contain the following properties:

  • Server - connection string containing the server address;
  • Port – contains a number indicating the server port; by default, depending on the connection type, you can specify 80 for unsecured connections or 443 for SSL secured.
  • Username – indicated if authorization on the server is required;
  • Password – user password on the specified resource;
  • Proxy – can contain an InternetProxy type object, indicated when a proxy is used to communicate with the server;
  • Secure Connection – default value is FALSE, switching to TRUE indicates the use of the https protocol.

In addition, the HTTPConnection object has its own methods, calling which allows you to more fully describe the handler execution algorithm:

  • CallHTTPmethod – contains two required parameters, HTTPmethod and HTTPrequest, supports the ability to write the response body to the file specified in the third parameter;
  • Write – using a PUT request, sends data to the server;
  • Modify – modifies an object by processing PATCH requests;
  • SendForProcessing – a method indicating the use of a POST request, as in all previous methods, must contain the text of the request, and can also transmit the address of the response file for recording data;
  • Receive - this will be discussed in more detail below;
  • GetHeadings is another method that will be used in the article;
  • Delete is actually a Delite request that removes the resource passed in the request from the server.

In the second line we create a request to the selected site, the text of our request contains one slash, which means that we want to receive home page. If the slash were followed by any expression, for example “page2” or “news”, we would get a different page.

The third line executes our request to the server.

In the fourth we show the result.

Handling http request redirection

Let's imagine a situation where we need to programmatically get a search result through any search engine by the key “Requests in 1s”. The section of code required to access GOOGLE is shown in Fig. 3

Fig.3

Here, in addition to the structures already familiar to us, there are Headers and Status Code. Let's deal with them.

Status Code – standard value specified in “Request for Comments”, can take the following values:

  1. If everything is fine, the value will return in the range from 100 to 299;
  2. In case of redirection, a code in the range from 300 to 399 will be returned; in our case, a successful permanent redirection to a resource will be determined by code 301;
  3. If there are errors in the request, the parameter will take a value from 400 to 499;
  4. A value in the range 500-599 indicates problems with the server.

Each page has a title, in the text of which several parameters can be distinguished (Fig. 4):

  1. Connection diagram (everything that comes before two slashes “//”);
  2. Address bar connections;
  3. Username and password;
  4. Port and host to connect to.

It is this splitting that is performed by the SplitAddressLine function. Having thus received a new address, we can save the page on our computer and open it in the default browser (GetPage procedure).

Fig.5

There are no new functions or ways to work with requests here, we are actually creating text document from the body of the site and launch the page in the browser.

We place the file at the root of drive D and call it test.

We take the image from the site

A natural question arises: if we do not need the entire site, but only need to obtain its individual elements, can this be done and how? Yes you can. The program code that allows you to do this is presented in Fig. 6

Fig.6

As you can see from the figure, in the body of the request we have the code of the site structure element that we need to receive. This part was not in our previous description and we need to dwell on this point in more detail.

We used a browser Opera to access the site. It has one important tool for us; when you right-click on an element, you can call context menu, one of the items is “View element code”.

It is thanks to him that we can obtain the address that will be used in the request Fig. 7.

POST request

Unlike simple Get requests, POST http requests have a text body, which can be stored either in plain text form or in the form of files with the extension xml, soap, json. There are quite a lot of tools on the network for creating request texts that allow you to debug and monitor the execution of certain requests.

In 1C, in order to launch a request with a specific text, the HTTP request object has the SetBodyFromString procedure.

Share