How we solved Magento core_url issues once and for all

website speedToday, we tell you the story of how we solved our Magento core_url issues, which might be very familiar and similar to you, since you’ve landed here.

If you have been working with Magento for long enough, chances are that sooner than later you’d have learnt that there are a two fundamental steps to follow if you are having issues:

  • It’s always the cache! Whenever something doesn’t work, clear the cache, and I mean, all of them! And the problem will (or not) go away, it’s like rebooting in Windows!
  • If that doesn’t work, run a full reindex, and repeat the above step 🙂

That’s why it’s is not only recommended, but also it’s very common to run reindexes daily, and most likely even more often, whenever we want to immediately reflect some changes in the front-end, such a new stock, new products, or new category-products assignments.

Reindexing often shouldn’t be a problem for small stores, but the more products you have, the more probable is that reindexing is going to cause you trouble due the long time that it takes, the amount of resources that it needs, and so on, as well as the fact that they need to run overnight to avoid slowing down your website dramatically.

Whenever a full site reindex is taking a long time, 99% of the cases “catalog_url” reindex is the one spending most of the time, specially if you have a large catalog of products and or if you have a multi-store setup.

Unfortunately or otherwise, that was our case at our bikes shop. We had several thousand products and, even though a big part of them are currently disabled, the reindex was taking far too long, and the table core_url_rewrite was growing exponentially, causing other problems such as slowing down the backup process, or preventing the sitemap generation from running, due big SQL queries taking far too long to run.

Like in most existing online stores, all of these could have been easily avoided if it had been addressed properly from the beginning, but you know… could’ve, would’ve, should’ve… but didn’t!

So, there we were, with a core_url_rewrite table with over 210k rows, and expanding beyond maintainability. We had only two chances:

  • Upgrade the servers and ignore/delay the problem until we reach to this same stage in a couple of months time.
  • Tackle the problem and invest the time needed to find out what’s going on, and sort it out properly

We love challenges, and the first option wasn’t even considered, as it wasn’t a solution really, so we got our hands on it and started to look into the possible causes.

We identified two major problems looking into core_url_rewrite table:

  • There were several thousand rows with URLs about products that were disabled or not visible individually. We had already installed Dn’D module that skips the reindex of disabled and not visible products, speeding up the reindex by keeping them out of the table, but there “leftovers” from the past, before the module was installed. If you don’t have Dn’D Patch Index Url module installed, please take a few minutes and install it right know, it’s a must have for any Magento store.
  • We noticed a very interesting issue: Every time we run a catalog_url reindex, the number of rows of the core_url_rewrite table was increased, even when there were no recent changes on the catalog. So, basically, the table was growing and growing each time without any obvious reason.

In order to address the first problem, we built two queries to identify the rows corresponding to:

Disabled products:

SELECT count(*) FROM core_url_rewrite WHERE product_id IN 
(SELECT entity_id FROM catalog_product_entity_int WHERE attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code ='status') AND VALUE = 2 AND entity_type_id = 4);

Roughly 2k rows.

Then, we did the same to identify the products with Visibility set to Not Visible Individually:

SELECT count(*) FROM core_url_rewrite WHERE product_id IN 
(SELECT entity_id FROM catalog_product_entity_int WHERE attribute_id = (SELECT attribute_id FROM eav_attribute WHERE attribute_code ='visibility') AND VALUE = 1 AND entity_type_id = 4);

Nearly 8k rows.

Since the above queries return rows that correspond to products not being displayed on the front-end (because they are disabled of not visible in the catalogue), they can safely be removed without any impact on the front-end/SEO.

The infinite growing “duplicated” redirects on core_url_rewrite

This is a very interesting issue which is widely known among Magento developers. There is a good thread on StackExchange discussing it, where you can find responses of all kinds. In the above link, people have done a great job analysing the problem, and some even propose (partial) decent solutions to it, but given the complexity, many people have also given up to find a fix. In fact, Alan Storm himself, one of the most popular Magento developers advises so, and his answer is the second most voted, which by the way is pretty disappointing to say the least.

In our case, it was unacceptable to leave it as it was, because we were aware of the issue, and we had been delaying it for a while. Eventually, it was causing us serious issues, and it was having a pretty bad impact in the overall performance of our store, so it had to be addressed.

As we mentioned previously, every time we reindex the “catalog_url”, the amount of rows on the table core_url_rewrite was increasing, and we were trying to figure out why. It seems that the problem is caused when there are two or more products/categories with the same URL key. During the reindex, Magento checks whether the url key is unique, if it’s not, it appends the entity_id of the product to the URL to make it unique. The problem is that next time the reindex runs, it will do the same process, but the new URL (url_key + entity_id) already exists, so Magento tries increasing the numeric part of the url key (product’s entity id) until it finds a unused URL, and it keeps creating crazy redirects on each run.

One way to avoid/overcame this issue is making sure that the url_keys of the products are unique. We tested that approach in first place, running a query that modified the url_key (attribute of the product) of each product that has duplicates, appending the entity_id followed by a dash. Ie. product with id 123 and url_key “my_product” would become “my_product-123”. This way when the reindex runs, all URLs would be unique, and Magento wouldn’t have to do “his thing”. However, this would be messing up with existing URL redirects in core_rewrite_url and potentially would lead to broken links. Also, it wouldn’t fully resolve the issue, as nothing would be preventing new products from being created/imported with duplicated URL keys in the future, so we had to find a better solution.

Since many people had been investigating and debugging this problem, we decided to have a look at the proposed solutions on StackExchange and tested some of them. The solution proposed by @Simon was the one that made more sense for us, and it seemed to do the job. After applying the code changes, reindexing wouldn’t increase the number of rows of the table. However, the proposed solution was overriding the core, which is something we never do unless it’s something unavoidable. It’s a nice solution, wrapped  as an “unofficial Magento patch”, but we didn’t want to take the risk of having future upgrade/patching issues. So, we decided to create a new Magento extension, which would override the model Mage/Catalog/Model/Url.php, and we put Simon’s fix there, leaving the core untouched. It all seemed to work fine, and the table core_url_rewrite stabilised so the first step was done, now it was time for the tricky part, the remaining “leftovers”.

We had 210k rows (well, actually 200k after the previous clean-up of disabled/not visible products) on the core_url_rewrite table, but as we mentioned previously, we have only a couple of thousand products enabled in our catalog, so presumably many rows were not really “needed”, and most likely the “oversize” of the table was related to the infinite rewrites/redirects.

Looking at the latest entries of the core_url_rewrite, there was a clear pattern on the redirects:

Product_id Request_path Target Path
1234 product-url-key-1234.html product-url-key-1235.html
12 product-url-key-13.html product-url-key-14.html

Both, request_path and target_path look the same except for the number prepended to the url_key. So, we came up with this query to identify all the redirects:

SELECT count(*)  FROM core_url_rewrite WHERE product_id IS NOT NULL AND LEFT(request_path, length(`request_path`) - 5 - length(product_id)) = LEFT(target_path, length(`target_path`) - 5 - length(product_id)) AND
is_system = 0

Result: 195k rows!!! Unbelievable, we thought those rewrites were bad, but we didn’t expect that they would be THAT BAD!

Right, so we wanted to get rid of all that “junk” redirects, but we couldn’t just delete them. Well, technically we could, but that could have a very bad SEO impact, it would generate many 404 errors for products already indexed in the search engines, and there would be existing links to the products in newsletters, social media, etc. So we needed an strategy to fix it.

After a lot of testing and back and forth, eventually we did the following:

  • Create a table core_url_rewrite_tmp which contains only the redirects (can be done with one query, using create table as select):
    SELECT category_id, product_id, store_id, request_path, target_path  FROM core_url_rewrite WHERE product_id IS NOT NULL AND LEFT(request_path, length(`request_path`) - 5 - length(product_id)) = LEFT(target_path, length(`target_path`) - 5 - length(product_id));
  • Override Mage_Cms_IndexController::noRouteAction to capture 404 events
  • Then, whenever a 404 happens, check if the request matches the below pattern: '/\\/(.*)-\\d+.html/'
    • If it doesn’t, let Magento handle the 404 normally
    • If it does, query our new table and search for matches on the current store, fetching the product_id or category_id.
      • If there are no matches, let Magento handle the 404 normally
      • If there are matches, redirect the user to the corresponding product/category (301 – permanent redirect) URL

After having this kind of fallback mechanism, the redirect entries can be removed from the core_url_rewrite table.

Then, the core_url_rewrite table is completely clean and light, which, being one of the critical and most used and joined tables of Magento,  has a shocking impact in the overall performance of the site, not to mention the massive speed increase on heavy tasks such as sitemap generation, core url reindex, and so on.

In our case, as a result of this whole clean-up, the core_url_rewrite now has 5k rows. We still have to keep the “core_url_rewrite_tmp”, but that table is queried in very rare occasions (vs the frequency of core_url_rewrite), when an “old URL” is requested.

Last but not least, since we are sending all the requests to the right destination with a 301 redirect, within a couple of months, all the search engines would have updated their indexes and the table can be “safely” removed.

Note that there might still be old links in emails, social media, etc., but the fallback can then be amended to redirect users either to the catalog search (searching for the requested URI), or it can do a best-match on the core_url_rewrite and redirect the user to  the closest match. This won’t be 100% accurate as the products with the duplicated URL keys would have similar url keys/names. However, on this fast moving age, after 6 months those links are highly unlikely to be hit.

We deleted our table after 3 months, and two weeks later we haven’t found any related 404 in our reports.

Finally, our zipped database (excluding orders) dropped from 80Mb to 5Mb, all our sitemap generation and reindex issues were resolved, and our website overall speed/performance improved noticeably, so we couldn’t be happier 🙂

Speeding up Magento (evil extensions)

Speeding up magentoSpeeding up Magento is the headache of many shop owners, which usually contact us frustrated after seeing how their page loads are way above the market average, causing a really high bounce rate.

Last week, we were tasked to investigate some spikes in a website that were even temporarily bringing the server down in some cases. Thankfully, our customer had Newrelic, which was very helpful pointing us to the right direction.

Tinkering around within Newrelic, we noticed quite a few issues:

  • It was a multi-website shop, with four different domains. One of them had the “shell” folder had directory listing enabled, and some malware/malicious attempt/bot had been crawling it and calling multiple times a php script that performs heavy operations, and was taking nearly 30 minutes to finish.
  • There quite a few 404 entries in newrelic with a load time suspiciously high.
  • There were also some other pages with an abnormally high load time, and they were all coming from the same user agent, also the 404 ones, which seemed to be some sort of SQL injection attempts by the looks of the URLs:
/checkout/-1%22%20OR%203%2b936-936-1%3d0%2b0%2b0%2b1%20--%20/add/uenc/aHR0cDovL3Nob3AuZ2Zpbml0eS5uZXQvb3RoZXItc3R1ZmYuaHRtbA,,/product/13953/form_key/5kBBRqrthVBCRE1e

/category-name/subcategory/shop%25'%20AND%202%2b1-1-1%3d0%2b0%2b0%2b1%20AND%20'FAEs'%21%3d'FAEs%25/custom-filter/custom-filter-2.html

Which url-decoded looks like:

/checkout/-1" OR 3+936-936-1=0+0+0+1 -- /add/uenc/aHR0cDovL3Nob3AuZ2Zpbml0eS5uZXQvb3RoZXItc3R1ZmYuaHRtbA,,/product/13953/form_key/5kBBRqrthVBCRE1e
/category-name/subcategory/shop%' AND 2+1-1-1=0+0+0+1 AND 'FAEs'!='FAEs%/custom-filter/custom-filter-2.html

Then, we started looking at the stack traces, and spotted a third party extension which was the slowest part of execution flow:

Slowest components Count Duration %
QuBit_UniversalVariable_Model_Uv::_getLineItems 1 11,100 ms 31%

A quick look at the code confirmed our suspicions, we had stumbled upon with another evil magento extension.

The third party extension had an observer on the basket that for each item, was loading the product, and for each product, it was loading each of its assigned categories, with the purpose of generating a JSON file for tracking purposes. It was running “fine” with low traffic, and with a low amount of items in the basket, but the load time was growing exponentially was new products were added to the basket, with together with a malicious behaviour adding over 400 products, was capable of bringing the server down. Interestingly, the tracking code was only required for the checkout cart page, but it was actually running on every single page, which is why the 404 and other non-cached pages were affected.

We deactivated the quotes with over 200 products in the basket (ie. not real users), did a few amendments in the code and deployed the changes. Below you can see the result:

Speeding up magento

A massive drop in the average response time, and guess what? the spikes problems disappeared 🙂

Once again, please be aware of the risks of installing third party extensions in your store, as they can really hinder the user experience of your customers and damage your business.

If you are facing similar problems with your store, do not hesitate to get in touch.

7 ways to speed up Magento on micro-instances

Magento Speed upThere are plenty of posts showing ways to speed up Magento, and this is yet another one, but we’ll try to come up with some original techniques that hopefully will help you to improve the performance of your store.

It’s worth mentioning that all the below tips have been tested thoroughly, and we are currently taking advantage of them on many production websites. Today, we’ll focus on how we did it for our motorbikes shop.

The infrastructure where store is currently running is a skinny micro-instance with 0.6GB of RAM, shared CPU, and 10GB of hard disk. We’ve been constantly tunning all the settings trying to get the best performance out of it, and here you have the major changes that made a difference:

  1. Cache:
    1. Start using FPC (Full Page Cache) if you aren’t already, this is definitely the most important thing and should be your priority. We recommend Varnish + Turpentine even though it might not be the easiest to install & configure, depending on the amount of customizations of your store. Having FPC is essential to be able to scale and handle multiple simultaneous requests, specially important in a machine where you’ve a very limited amount of RAM and CPU.
    2. Start using the PHP extension APC (or OPCACHE + APCU) and use APC for the Magento config cache (you can enable it on the local.xml). This will definitely increase the speed of your site for the non-fpc-cached pages.
  2. Memcached for sessions: Many other people use Redis, and that might be even better, but as long as you don’t use files or database, you are doing just fine. You can change this setting in your local.xml. If you don’t use it for anything else, you don’t need more than 16/32MB until you have a serious amount of users.
  3. Robots.txt & Blocking bots: When you start running a new website, (I completely made up that number) probably around 90% of your traffic is just bots, and even when you grow, it will still represent a very high amount of the number of requests of your site. Therefore our advise is to spend some time carefully tunning your robots.txt to prevent bots from crawling pages or images or whatever you don’t want to be crawled. Moreover, there are just too many bots out there and unless you are really interested in them all to crawl your site, my advise is to block them either by the robots.txt or by any other mechanism, such as nginx/apache config by looking at the user agent. Depending on how aggressive you are willing to be against unwanted bots, you can drastically reduce the number of requests of your server and save all those resources to delivery a much better user experience (aka speed) to your actual customers.
  4. Reindex optimizations: This is one of the most painful tasks, and it can only get worse as your catalog of products increase. A good start is usually installing this url reindexer patch extension, which basically skips the disabled or not visible products from the catalog url reindex. This has proven to reduce the reindex time from nearly an hour to just a few minutes, but it will depend a lot on your catalog of products.
  5. MySQL tunning: A proper configuration of your mysql instance can have a huge impact on your site’s performance. Give mysqltunner a go, it will most likely give you useful advise, but once again, it all depends on each custom setup. One setting that works well for one store, might not work for another one. Always monitor your config changes and if something doesn’t work, revert the changes.
  6. Varnish/Turpentine tunning: After installing and configuring varnish+turpentine, we noticed a boost in the capacity and performance of the site, but we noticed as well that, due the CSRF protection (aka formkeys) added in the latest versions of Magento, Turpentine’s FPC stopped working as it used to do. Now, the default config will never serve a cached page to a new user, as it has to be processed by Magento in order to generate a valid session and be able to get a valid formkey through an ESI. This is a very good solution to preserve the FPC functionality and the CSRF protection, but has the downside of always giving a non-cached page to the first time visitors, which if it’s not quick enough, can make de difference between bouncing or not. Therefore, we recommend enabling the setting “Use VCL Fix” in Turpentine, but that would imply disabling the CSRF protection as well, which you should do at your own risk. Be very careful doing this as this can potentially break the add to cart and login in your store. Please, note that after enabling that setting you might still notice that the first time visitor still gets served a non-cached page, if thats the case, check this pull request and make sure you have those changes in your VLC file.
  7. CDN: This one is pretty obvious, but somehow many people have the impression that having a CND is very complex and/or costly. We’ve tried a few of them and the best one we’ve found so far is KeyCDN (register through this link and you’ll get 10$ credit month trial!), mostly because of the price, but also because of the performance and the features.

Conclusion: The above list is a small subset of the many ways to speed up Magento, and it should be just the beginning of all the things that you should do in order to make your store quicker and better. There is an endless list of things that you can try, but this list highlights the changes that had a bigger impact in our micro-instance, and hopefully will be useful for not only low resource machines, but also for the majority of the Magento store setups.

If you have any questions and/or need any help, do not hesitate to get in touch.

Evil Magento extensions

Evil Magento Extensions I: Email marketing

magentoMagento Commerce is a really good source of extensions, some of them are excellent, some of them are cheap or even free, some of them are not great… and some of them are what we call “Evil Magento Extensions”!

First of all we would like to apologise if anyone gets offended, is not the purpose of this post, but to contribute to Magento ecosystem by sharing our experiences and always aiming to improve Magento community.

We always want the best Magento Extensions for our store and we want to spend as less money as possible, but depending on your specific shop needs you might want to think twice before installing one of those Evil Magento Extensions.

There are way too many extensions out there and we obviously haven’t tried them all, but from our experience usually the ones provided by email marketing platforms are the more interesting ones. It is very common to use a third party service to send the transactional emails, and they mostly tend to provide a free (and evil) Magento Extension which is pretty much “plug & play” into your store, so that you don’t have to worry about anything…. or maybe should you?

The usual case scenario is that when a user places an order, or creates a new account, an email is sent, and we’ve found out that most of this extensions relay on Magento Observers (ie. customer_register_success, sales_order_place_after), or even rewrite the Mage_Core_Model_Email_Template class in order to push the data to their servers. That’s fine until here, if it wasn’t because almost all of them do it synchronously, ie. on the same request made by your customer. This means that for instance, when your user is placing a new order, she has to wait until your server communicates with your email platform: sending the data of the order, and waiting for a response. This is a “standard” process that usually doesn’t take longer than a few milliseconds/seconds which is fine… hang on, are you sure?

The astonishing truth is that this process can (and will) hinder your customers experience in many different ways, but you probably already know about them if you are reading this post. A request from your store to an external server can take any time from few milliseconds to several seconds, minutes, or even hours! and many things can go wrong while that’s happening, often leading to an error displayed to your customer, or just to another abandoned cart, tired of waiting.

Ok, I might be exaggerating a bit, there would surely be a timeout error somewhere between this process that would prevent a request to take hours, but sadly, we have seen MANY cases where a request would hang for several minutes. Of course, it’s not the usual case, but it will happen sooner than later. It happens to Amazon, to Facebook, even to Google, it happens to the best of us, it might be due networks issues, power failures, too many concurrent users, or whatever reason.

The point is that your store, your customers, will be affected and if they have a bad experience they will not come back. Bear in mind that it’s not necessary a delay of minutes to impact your sales, just a few extra seconds can prevent your customers from having a successful check out experience.

The reason behind it is that when a new order is placed, Magento has to do quite a few database operations (decrease the stock of all products, insert all order items, save all customer data, etc. and finally create the order in sales_flat_order table) and in order to preserve data integrity, it wraps them all within a DDL transaction in the database. In other words, Magento will execute all these operations all at once, so that if an error occurs, the whole transaction will be rolled back and no changes will be done in the database.

So, what happens if there is an observer in the middle of this complex process that performs a request to the external server of your email provider? Yes, it will delay the process a few seconds. You might thing that it’s not a big deal, but did you know that while this is happening, some tables of your database are being locked by this transaction, which is effectively preventing new orders from being placed until that transaction has finished?

In other words, if two customers are placing an order at almost the same time, the second one will be blocked by the first one. Internally, the queries in the database will be queued up and executed as soon as the first transaction finished, but queued queries will wait for a limited time (typically 50 seconds) and if the lock hasn’t been released by then, an error/exception will be thrown and therefore the order will not be placed. It might seem an unlikely event, but unfortunately, It happens more often than we wish, and the more concurrent customers you have, the more probabilities for this to happen.

From our experience, we’ve seen it happening in shops with low traffic during sale periods, usually when there are around 100+ concurrent users it can happen a few times day, and with 300+ concurrent users it can happen almost every hour.

If you see something like this in your exceptions.log you might well be suffering from one of those Evil Magento Exceptions:

exception 'PDOException' with message 'SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction' in .../lib/Zend/Db/Statement/Pdo.php:228

.../app/code/core/Mage/Sales/Model/Resource/Order/Abstract.php(425): Mage_Core_Model_Resource_Db_Abstract->save(Object(Mage_Sales_Model_Order))

We’ve seen this issue with many popular Magento Extensions and well known email marketing platforms such as Dotmailer, ExactTarget or Sailthru, so next time you are in about to install a new Magento Extension, make sure it’s reviewed first by a Magento Expert to avoid awkward surprises.

If you stumble upon with this issue, do not hesitate to get in touch, and we will be able to assist you.

Proxy & Debug SOAP Requests in PHP

crm_soap_php1Last week, we needed to debug soap requests in php due an issue on our live environment. We attempted to integrate our Magento store with an external provider that used SOAP to communicate to our severs. Everything was working fine on our testing/staging environments, but when we went live, they contacted us due an issue in the connection:

Error Fetching http body, No Content-Length, connection closed or chunked data.

After a quick search, we found an easy fix by falling back to HTTP 1.0 on the client connection:

$client = new SoapClient("http://yourserver.com/api/?wsdl",

array(
 'stream_context' => stream_context_create(array('http' => array('protocol_version' => 1.0) ) )
 )
 );

However, to make things more interesting, the external service wasn’t willing to do any changes on their side, which means that we needed to find another solution, on our side.

It did seem to be an environment related issue rather than a code problem. Anyway, we tried to debug the whole connection flow, to see if we could find clues of how to solve the problem.

We have a sample script that we use to debug connections locally (using xdebug), with the code below:


class Debug_SoapClient extends SoapClient
{
 public function __doRequest($request, $location, $action, $version, $one_way = 0)
 {
 $aHeaders = array(
 'Method: POST',
 'Connection: Close',
 'Content-Type: text/xml',
 'SOAPAction: "'.$action.'"'
 );

$ch = curl_init($location);
 curl_setopt_array(
 $ch,
 array(
 CURLOPT_VERBOSE => false,
 CURLOPT_RETURNTRANSFER => true,
 CURLOPT_POST => true,
 CURLOPT_POSTFIELDS => $request,
 CURLOPT_HEADER => false,
 CURLOPT_HTTPHEADER => $aHeaders,
 CURLOPT_SSL_VERIFYPEER => true,
 CURLOPT_COOKIE => "XDEBUG_SESSION=PHPSTORM"
 )
 );

$ret = curl_exec($ch);
 return $ret;
 }
}

//Use soap as always, just replacing the classname while instantiating the object:
$client = new Debug_SoapClient('http://myurl.com/api/?wsdl');

However, we can’t use xdebug on live environment, so we tried to find a way to debug the connection flow, so that we could compare the differences of using HTTP 1.0  vs the standard way.

After a couple of unsuccessful attempts, we found a hacky way to proxy-forward the requests through curl, so that we could debug the input and the output of each call.

Firstly, we created a test php file that would log the environment variables on a file for each request, and then it would forward it to the soap api:

file_put_contents(__DIR__ . '/soap.log', var_export($_SERVER, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($_REQUEST, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($_GET, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($_POST, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($HTTP_RAW_POST_DATA, true), FILE_APPEND);
file_put_contents(__DIR__ . '/soap.log', var_export($_GET, true), FILE_APPEND);
// Override the REQUEST_URI variable so that the framework can understand and process the soap request
$_SERVER['REQUEST_URI'] = '/api/?wsdl';
include 'index.php';

Note that sometimes $_POST might be empty but $HTTP_RAW_POST_DATA might contain data.

After comparing the logs of the two requests, we noticed few differences on the $_SERVER variables, but even overriding the values didn’t help.

More research revealed that the issue might be related with a bug in some php versions:

It is an HTTP 1.1 issue with some versions of PHP not properly decoding chunked data (even some versions of PHP 5.3.x will still see this error, the documentation on PHP’s official site is wrong). You have two options in this case:

(1) Update your version of PHP to 5.4.x or later.
(2) Force the client library to use HTTP 1.0

We tried to connect using another app-server with an older php version, and it worked without having to fallback to http 1.0.

Then we thought about proxy-ing the requests to the other server internally, just to test if that would solve the issue. Eventually we managed to get the requests forwarded with another script file:


header('Content-Type: text/xml; charset=UTF-8');
$ch = curl_init('https://external.server.com/soaptest.php/' . ($_GET ? '?' . http_build_query($_GET) : 'api/index/index/'));
curl_setopt_array(
 $ch,
 array(
 CURLOPT_VERBOSE => false,
 CURLOPT_RETURNTRANSFER => true,
 CURLOPT_POST => $_SERVER['REQUEST_METHOD'] == 'POST' ? true : false,
 CURLOPT_POSTFIELDS => $HTTP_RAW_POST_DATA,
 CURLOPT_HEADER => false,
 CURLOPT_SSL_VERIFYPEER => false,
 CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
 )
 );
$ret = curl_exec($ch);
file_put_contents(__DIR__ . '/soap.log', var_export($ret, true), FILE_APPEND);
echo $ret;

With this script curl-ing the previous one on the other server, we managed to get the SOAP calls to work. It’s something that we did only for debugging purposes, but hopefully it can be useful for you in case you face similar issues and you want to discard possibilities. Sadly, that still didn’t solve the issue for the non http 1.0 requests, but at least we were able to compare all the inputs and all the outputs and we noticed that it was all working fine both ways until the actual soap call:

– Instantiating the class with the url

– Authenticating to soap by calling login()

– Calling to a custom soap call: ie. sales_order.info would return the proper results in both cases, but only the http 1.0 connection would properly retrieve them. The standard connection would retry the call and eventually display the error message showed below.

Eventually, we solved the issue by temporarily making the requests directly hitting the old php version server, until the php version was upgraded on the non working server.