Category: "Web Applications"

Quick Apache Benchmark test with log in

I have been looking at performance for a web application. I am using ab to compare the response times while adjusting the database indexing.

# 'log in'
curl -L https://www.example.com/login.php -k -b cookie -c cookie-jar -d username=admin -d password=**** -s > /dev/null
# Run the test -n times (in this case 10)
ab -C `grep PHP_session cookie-jar | cut -f6,7 | tr '\t' '='` -n 10 'https://www.example.com/main.php'

What's happening?

The curl command is logging in to the application and saving the cookies in a file named cookie-jar.

The grep command is extracting the session id cookie - name and value - from the cookie-jar and using it to authorize access to a main page for ab.

This post courtesy of Game Creek Video

DataTables - Adding Buttons to a FixedHeader

I was using both the FixedHeader and Buttons extensions of DataTables and I needed the buttons to remain visible when the user scrolled.

The solution I chose was to move the buttons DOM into the table head.

Since the buttons are in a div and thead is part of a table, a tr is used with a td that has a colspan of the entire table.


var dtButtons = document.querySelector(".dt-buttons");
var row, cell;
row = document.createElement("tr");
cell = document.createElement("td");
cell.id = "magic-button-row";
cell.setAttribute("colspan", /*** Put the number of columns in your table here ***/);
cell.appendChild(dtButtons);
row.appendChild(cell);
document.querySelector("#display-table thead").insertBefore(row, document.querySelector("tr:first-of-type"));

There is a tiny bit of CSS, too.

#magic-button-row {
    padding: 0;
    margin: 0;
    text-align: left;
}

This post courtesy of Game Creek Video.

Web application session timeout code

Session timeout warning for a web application with a variety of page layouts and frequent use of multiple tabs.

Nutshell explanation - ping the server, if you get a 403, show a huge red bar across the top of whatever page pinged.

Result:

  • It is immediately apparent the tab has timed out
  • If one tab times out, it does not disrupt the others
  • There is a link to help the user log in again

function sessionPing() {
    var pinger;
    function doPing() {
        var pReq = new XMLHttpRequest();
        pReq.addEventListener("load", function () {
            var sessionExpired, sessionExpiredMessage;
            var reloadLink;
            if (this.status === 403) {
                clearInterval(pinger);
                sessionExpired = document.createElement("div");
                // Sometimes an inline style is really the best solution
                sessionExpired.setAttribute("style","display:block; width: 100%; line-height: 2.5em; position:absolute; top:0; z-index:10000; text-align: center; background-color: #f00; color: #fff; font-family: 'Trebuchet MS',sans; font-size: 1.5em; font-style: italic");
                sessionExpiredMessage = document.createTextNode("Session expired ");
                sessionExpired.appendChild(sessionExpiredMessage);
                reloadLink = document.createElement("a");
                reloadLink.href = location.href;
                reloadLink.textContent = "Click to Continue";
                reloadLink.setAttribute("style","font-size:0.7em;color:#ddd");
                sessionExpired.appendChild(reloadLink);
                document.body.insertBefore(sessionExpired, document.body.firstChild);
                document.title = "Session expired";
            }
        });
        pReq.open("GET", "/ping.php");
        pReq.send();
    }
    pinger = setInterval(doPing, 30000);
}
sessionPing();


<?php
session_start();
if (empty($_SESSION['user_id'])) {
    header('HTTP/1.1 403 Forbidden');
    exit;
}
header('HTTP/1.1 201 No content');
exit;

One may argue that the page should be cleared, in this case, I chose to leave it up so people can copy the content off.

Confession: I didn't test this code, it is an extract.

This post courtesy of Game Creek Video

Symfony 3 Ajax (EntityType) CollectionType

Objective: Create an efficient way to use a Symfony CollectionType of Entities. I wanted to be able to provide a Dijit FilteringSelect (autocomplete) for a collection of entities.

Issues: Using a CollectionType of EntityTypes loaded all the entities into a selection statement. This is fine if there are only a few entities, but not if there are hundreds. Also, model transformers cannot be used with CollectionTypes.

Solution: Create a custom FieldType and apply a model transformer.

The custom field type is based off a TextType, which allows the id of the entity to be passed as text, then transformed by the transformer. Transformers are described here.

The Dijit FilteringSelect only needs an id to serve as an anchor within the page layout. One may argue it could all be dynamic, but I like to use the HTML as a foundation. A text input is used as the base element which is replaced by the Dijit widget. The widget is created and populated with the data from a store.

<?php

namespace AppBundle\Form\Admin\Asset\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use AppBundle\Form\Admin\Asset\DataTransformer\ModelToIdTransformer;

class ModelRelationshipType extends AbstractType
{

    private $modelToIdTransformer;

    public function __construct( ModelToIdTransformer $modelToIdTransformer )
    {
        $this->modelToIdTransformer = $modelToIdTransformer;
    }

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm( FormBuilderInterface $builder, array $options )
    {

        $builder
                ->addModelTransformer( $this->modelToIdTransformer );
    }

    public function configureOptions( OptionsResolver $resolver )
    {
        // Stub
    }

    public function getParent()
    {
        return TextType::class;
    }

}

The new custom field type was then added as a service, with the transformer injected in:

    app.admin.field.type.model_relationship:
        class: AppBundle\Form\Admin\Asset\Type\ModelRelationshipType
        arguments: ['@app.form.data_transformer.model']
        tags:
            - { name: form.type, alias: 'app_model_relationship_type' }

The Twig template code to display each row of the collection uses a macro, because there are four of these fields.

{% block _model_extends_entry_row %}
{% import 'form/macros.html.twig' as form_macros %}
{{ form_macros.model_relationship_entry_row('requires',form) }}
{% endblock %}

{%macro relation(title,type,form) %}{%spaceless%}
        <div id="{{type}}" class="{{type}}">
            <h3>{{title}}</h3>
            {{form_row(form)}}
            {% if form.vars.allow_add %}
                <div data-type="{{type}}" class="add-one-more-row">{{ 'common.add_one_more'|trans}}
            {% endif %}
        </div>
{%endspaceless%}{%endmacro %}

To use the new field type in the form, you add the CollectionType, and the entry_type is ModelRelationshipType.

->add( 'requires', CollectionType::class, [
                    'entry_type' => ModelRelationshipType::class,
                    'required' => false,
                    'label' => false,
                    'empty_data' => null,
                    'allow_add' => true,
                    'allow_delete' => true,
                    'delete_empty' => true
                ] )

How to set environment variables that can be referenced by Apache, shells and cron jobs

In some cases, environment variables are used to store configuration information. If the values are referenced from different sources, such as through a web server and on the command line, it is a good idea to define them in a single place and gracefully make them available.

This is one way to set the environment variables in one place, then source them into accounts for use (CentOS 6.4):

1. Create /opt/webapp/etc/appconfig/common and put the environment variables in it

export TEST_ENV_VAR="Test Environment Variable"

2. Add these two lines to /opt/webapp/etc/sysconfig/httpd

if [ -f /opt/webapp/etc/appconfig/common ]; then
. /opt/webapp/etc/appconfig/common
fi

3. Add these two lines to /etc/sysconfig/httpd

if [ -f /opt/webapp/etc/sysconfig/httpd ]; then
. /opt/webapp/etc/sysconfig/httpd
fi

4. Add this line to /etc/httpd/conf.d/webapp.conf (webapp Apache conf file)

PassEnv TEST_ENV_VAR

4. Restart Apache with service httpd restart

5. Test with http://webapp/phpinfo.php (<?php phpinfo(); ?>

6. Add these two lines to /home/admin/.bashrc - or whatever the account is that will use the variables.

if [ -f /opt/webapp/etc/appconfig/common ]; then
. /opt/webapp/etc/appconfig/common
fi

7. Test with echo $TEST_ENV_VAR

What this does is creates a common place to store the environment variables and makes them accessible to both Apache and the shell of admin (and any other account that includes them). That way, when a script is run as a cron job or on the command line, it has the environment variables all set. If new environment variables are needed, or if they change, the common file is updated as well as any others that reference the new variables. Then you restart Apache.

1 3 4 5 ...6 ...7 8 9 10