Monday, December 28, 2009

한국의 S/W 품질이 낮은 이유

나는 한국의 소프트웨어 품질이 낮다고 생각한다. 건축가가 자신이 설계한 건물에서는 못잔다는 말처럼 직업이 프로그래머이니까 그렇게 느낄 수도 있겠다고 생각할지 모르겠으나, 시간이 지나면 지날 수록 이러한 생각이 확고해 지기만 할 뿐이다.

소프트웨어 품질이 낮다고 동의하는 대부분의 사람들이 개발자의 수준을 먼저 얘기하곤 한다. 심지어 개발자들도 스스로의 수준을 얘기하는 경우가 더러 있다. 하지만, 이건 아니다. 수압이 낮아서 물이 안나오는 데 애꿋은 수도꼭지 탓만 하는 것과 비슷하다.

한국의 소프트웨어 품질이 낮은 이유는 프로그래머의 수준보다는 프로그래머의 의견이 무시되는 개발속도/기능 위주의 개발 환경 때문이다.

프로그램 개발에 관련해 여러사람들과 얘기하다보면 항상하는 얘기가 있다. 어느정도 규모나 완성도를 요구하는 소프트웨어의 개발은 고층건물을 짓는 것과 비슷하다는 얘기다. 단층 주택은 별 고민 없이 뚝딱뚝딱 지을 수 있다. 하지만 100층짜리 건물은 얘기가 다르다. 지반 공사 부터 시작해서 하중 계산, 고층에서의 진동, 내장재의 물리적 특성, 전기 분배, 상하수 배관 시설, 낸난방 및 공조 등의 각 부분에서 물리, 전기, 인체공학 등등의 여러 기술이 복합적으로 사용되어 건물이 완성된다. 프로그램도 마찬가지다. 조엘 온 소프트웨어에는 웹페이지에서 간단한 파일을 업로드 하는 페이지를 예로들고 있다. 수 Kb의 파일을 업로드하는 코드는 10분이면 완성이다. 하지만 Gb라면 어떨까. 파일 업로드 상태를 모니터하는 쓰레드와 서버와 주기적으로 통신하여 상태를 확인하는 코드, 업로드 중에 발생하는 여러 상황에 대한 고려, 업로드 실패시 전송된 부분에 대한 후처리 등 생각할 일이 많다.

하지만 이를 인식한 개발자의 의견이 반영되는 경우는 매우 드물다. 일반적으로는 그냥 업로드 파일의 크기를 수 Mb로 제한하고 대신 다른 다양한 기능을 더 추가하는 방향으로 진행된다. 기능적인 완성도를 추구하는 개발보다는 여러 가지 기능들을 빠른 시일에 화려한 UI와 개발하는 방향으로 진행된다. 굳이 일일이 예를들지 않아도 우리식(?)으로 개발된 프로그램들을 보면 대부분 이런 식이다.

A라는 거대 고객이 X라는 프로그램을 만들어 달란다. B사는 요구하는 기능을 충실히 구현하고 에러가 발생하지 않도록 많은 상황을 고려하여 신뢰성있는 소프트웨어를 개발하는데 집중한다. C사는 B사의 소프트웨어 보다 더 많은 기능을 넣고 화려한 UI로 장식한다. 고객 입장에서 최종 제품을 테스트 해보니 둘다 원하는 기능이 동작하는데 C사는 더 많은 기능을 제공하면서 더 이쁘다. 그래서 C사를 선택한다. 나중에 고객은 미처 예상하지 못했던 오류를 C사의 제품에서 발견한다. B사는 이러한 경우까지 고려했다고 처음에 설명했지만 고객은 오류를 직접 당하기 전까지는 이해하지 못한다. 결국 이런식의 제품 개발이 반복되어 B사도 다기능의 화려한 외양의 소프트웨어 개발에 매진하게 된다.




Friday, December 25, 2009

Brief on Signal

Review SIGNAL

Signal can be used to control process. SIGKILL, SIGSTOP, SIGINT are well known types of signal. No process can mask SIGKILL and SIGSTOP.

In a program, you can register signal handler function so that when a specific signal is sent to a process it can invoke registered signal handler function.

And also you can block in a certain part of the code to wait signal.

In multi-thread program, we can set signal mask so that only specified thread can handle the signal.

* How to register signal handler.

Though we've used the function signal() before, it's ANSI compatible one but not a standard. We can use sigaction() in the standard.

sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

If handler is registered with the function above, handler will be called when the signal occurs.

We use struct sigaction structure to register handler, to set mask, flags.

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *); // kind of advanced
    // used with sigfillset(), sigemptyset(), sigaddset(), sigdelset()
    sigset_t sa_mask;  
    int sa_flags; // option flags like
    /*
        SA_NOCLDSTOP, // child의 중지시 SIGCHLD 안받음
        SA_ONESHOT/SA_RESETHAND : 일회용 핸들러
        SA_NOMASK, SA_NODEFFER : 같은 시그널 발생을 block하지 않음
        SA_SIGINFO : 핸들러로 sa_sigaction을 사용
    */
    void (*sa_restorer)(void);
};


Sample code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

void usr_handler(int signum);

int main()
{
    struct sigaction sa_usr1;
    struct sigaction sa_usr2;

    memset(&sa_usr1, 0, sizeof(struct sigaction));
    sa_usr1.sa_handler = usr_handler;
    sa_usr1.sa_flags = SA_NODEFER;
    sigfillset(&sa_usr1.sa_mask);

    memset(&sa_usr2, 0, sizeof(struct sigaction));
    sa_usr2.sa_handler = usr_handler;
    sa_usr2.sa_flags = SA_NODEFER;
    sigemptyset(&sa_usr2.sa_mask);

    sigaction(SIGUSR1, &sa_usr1, NULL);
    sigaction(SIGUSR2, &sa_usr2, NULL);

    printf("SIGUSR1, SIGUSR2 Registered!\n");

    for (;;)
    {
        pause();
    }

    return EXIT_SUCCESS;
}

void usr_handler(int signum)
{
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("[%d]Caught signal. will sleep 1 sec : SIGUSR%d\n"
            ,i , (signum == SIGUSR1) ? 1 : 2);
        sleep(1);
    }
}


* How to block signal in a process

sigprocmask() offers signal blocking.

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

If oldset is not NULL, current signal mask will be copied into oldset.

* How to handle signal in multi-thread program.

As a default, if program receive signal, all of its thread receive the signal and it will make,. a chaos and program will halt. Generally, people set signal mask so that signal can be handled by specific thread.

Like sigprocmask, threre is pthread_sigmask(). If it's called in main(), all thread created after will have that signal mask. It it's called in a certain thread, only that thread will have that mask. In this way, we can specify which thread will receive signal.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>

#define THREADNUM 5
#define LIFETIME 10

struct s_thread_ctx
{
    pthread_t tid;
    int index;
}ctx[THREADNUM];

void sig_handler(int);
void *worker(void *);

int main()
{
    void *res;
    int i;
    sigset_t sigmask, sigmask_old;

    sigfillset(&sigmask);

    printf("[MAIN] Masking all signals\n");
    pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask_old);

    for (i = 0; i < THREADNUM; i++)
    {
        ctx[i].index = i;
        pthread_create(&ctx[i].tid, NULL, worker, (void *)&ctx[i]);
        printf("[MAIN] Create %dth thread\n", i);
    }

    printf("[MAIN] Joining threads\n");
    for (i = 0; i < THREADNUM; i++)
    {
        pthread_join(ctx[i].tid, &res);
    }
    return 0;
}

void *worker(void *arg)
{
    int i;
    sigset_t sigmask;
    struct s_thread_ctx *ctx;
    struct sigaction sa;

    ctx = (struct s_thread_ctx *)arg;

    sigfillset(&sigmask);
    sigdelset(&sigmask, SIGINT);

    sa.sa_handler = sig_handler;
    sigfillset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
    
#ifdef HANDLER_THREAD
    /* only last thread will catch SIGINT */
    if (ctx->index == (THREADNUM - 1))
    {
        pthread_sigmask(SIG_SETMASK, &sigmask, NULL);
    }
#endif

    /* Just waiting for LIFETIME seconds */
    for (i = 0; i < LIFETIME; i++)
        sleep(1);

    return arg;
}

void sig_handler(int signum)
{
    if (signum == SIGINT)
    {
        printf("[HANDLER] I've got SIGINT\n");
    }
    return;
}



* Handling signal and waiting signal.

If handler is registered, it would be called when specified signal is sent. And we can also wait until the signal arrives.

int sigsuspend(const sigset_t *mask);

Function sigsuspend will block the program until specified signals in mask arrive.

#include <signal.h>
#include <stdio.h>
#include <string.h>

void handler(int signal)
{
    printf("[Handler] Signal handled\n");
    return;
}

int main()
{
    int sig;
    sigset_t set;
    struct sigaction sa;

    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGUSR1);

    sigaction(SIGUSR1, &sa, NULL);

    sigfillset(&set);
    sigdelset(&set, SIGUSR1);

    sig = sigsuspend(&set);

    printf("SIGUSR1 arrived\n");

    return 0;
}




* Signal and process

If a signal is sent to a certain process, it's also sent to all of its child as long as they have same process group id. That means, if a child become a process group leader, signal will not be propagated to it any more.

cf. session = process groups and processes, process group = processes, if pid == pgid, it's process group leader.

related functions : setpid, setpgid, setpgrp, getsid, setsid

> kill -SIGUSR1 -1234 # to send SIGUSR1 signal to all processes which have pgid as 1234

Tuesday, December 15, 2009

When you want to make your function manage data in a thread-safe way.

When we want to make thread-specific memory area, we can use pthread_setspecific()/getspecfic() like below.

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

#define THREADNUM 30

struct ctx_t {
    pthread_t tid;
    int index;
} ctx[THREADNUM];

pthread_key_t t_key;

struct data_t {
    int var_a;
    char var_b;
};

void *thread_func(void *);
int sub_func(pthread_key_t);

int main()
{
    int i;
    void *res;

    pthread_key_create(&t_key, NULL);

    for (i = 0; i < THREADNUM; i++)
    {
        ctx[i].index = i;
        pthread_create(&ctx[i].tid, NULL, thread_func, (void *)&ctx[i]);
    }

    printf("Joining!\n");
    for (i = 0; i < THREADNUM; i++)
        pthread_join(ctx[i].tid, &res);

    pthread_key_delete(t_key);
}

void *thread_func(void *arg)
{
    int i;
    struct data_t *pdata;
    struct ctx_t *ctx;

    ctx = (struct ctx_t *)arg;

    pdata = (struct data_t *)malloc(sizeof(struct data_t));
    pdata->var_a = (ctx->index + 1) * 10;
    pdata->var_b = 0;
    pthread_setspecific(t_key, pdata);

    for (i = 0; i < 10; i++)
    {
        printf("Thread(%d) has value %d in its data space.\n",
            ctx->index, sub_func(t_key));
    }
    return NULL;
}

int sub_func(pthread_key_t key)
{
    struct data_t *pdata;
    pdata = (struct data_t *)pthread_getspecific(key);

    return pdata->var_a;
}

Monday, December 14, 2009

Barrier mechanism

While we make threads and make it work, sometime we want to make them stop at a certain point of work flow. Barrier can be used to make them wait till the other threads end their jobs. Here is an example. 5th thread will sleep 5 seconds and the other threads will wait with pthread_barrier_wait().

#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

#define THREADNUM 5

pthread_barrier_t barrier;

struct thread_ctx {
    pthread_t tid;
    int idx;
} thr_ctx[THREADNUM];

void *start_thread(void *);
void clean_thread();
void exit_w_err(const char *);

int main()
{
    int i;

    pthread_barrier_init(&barrier, NULL, THREADNUM);

    for(i = 0; i < THREADNUM; i++) {
        thr_ctx[i].idx = i;
        if (pthread_create(&thr_ctx[i].tid, NULL,
            start_thread, (void *)&thr_ctx[i])) {
            exit_w_err("thread_create");
        }
    }
    clean_thread();
}

void *start_thread(void *in_ctx)
{
    int res;
    struct thread_ctx *ctx;

    ctx = (struct thread_ctx *)in_ctx;

    /*
     * thread will wait with barrier
     */
    if (ctx->idx == THREADNUM - 1) {
        sleep(5);
        printf("thread (%d) arrived\n", ctx->idx);
    }
    else 
        printf("thread (%d) started to wait\n", ctx->idx);

    if ((res = pthread_barrier_wait(&barrier)) == EINVAL)
    {
        printf("\t errno : %d\n", res);
        return NULL;
    }

    return in_ctx;
}

void clean_thread(void)
{
    int i;
    void *res; // dummy pointer.

    for (i = 0; i < THREADNUM; i++) {
        pthread_join(thr_ctx[i].tid, &res);
    }

    return;
}

void exit_w_err(const char *msg)
{
    fprintf(stderr, "[ERROR] %s\n", msg);
    exit(0);
}

Thursday, December 10, 2009

13 ways to make your web page faster.

1. Remove trivial text - comment, tab, space, etc.
2. Cache ajax data if it's possible.
3. Set your css, javascript out of the page and include them. So that it would be cached.
4. Script, css optimization.
5. Adjust ETags.
6. Trim meta data of your img files. If you want resize it, send them as resized.
7. Use gzip compression option so that compressed data will be transmitted.
8. Javascript DOM Access is slow. Cache them if it's possible.
9. Set your css on the top so that browser can render your page as it gets data.
10. Browser can't download javascript in parallel way. Put them on the bottom.
11. Don't use css expression, filter.
12. Set cache in response header field.
13. Note that some mobile devices cache only page smaller than 25kb.

Wednesday, October 28, 2009

Sample thread code using mutex

For your education.


#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define THREAD_NUM 5
#define MUTEX_ENABLED 0

struct thread_ctx
{
    int id;
    pthread_t tid;
} ctx_arr[THREAD_NUM];

int fd;
pthread_mutex_t *mtx;
pthread_mutexattr_t *mtxattr;

void * t_worker(void *arg)
{
    int i;
    char buf[10];

    struct thread_ctx *ctx = (struct thread_ctx *)arg;
    sprintf(buf, "%d\n", ctx->id);

    if (MUTEX_ENABLED)
        pthread_mutex_lock(mtx);

    // critical section
    for (i = 0; i < 4; i++)
    {  
        write(fd, buf, strlen(buf));
        usleep(100);
    }

    if (MUTEX_ENABLED)
        pthread_mutex_unlock(mtx);

    return (void *)ctx;
}

int main()
{
    int i;
    void *res;

    // open file
    fd = open("result.dat", O_CREAT|O_RDWR);
    if (fd < 0)
    {  
        printf("[ERROR] file open failure - %s.\n", strerror(fd));
        return -1;
    }

    // init mutex
    mtx = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
    mtxattr = (pthread_mutexattr_t *)malloc(sizeof(pthread_mutexattr_t));
    pthread_mutexattr_init(mtxattr);
    pthread_mutexattr_settype(mtxattr, PTHREAD_MUTEX_TIMED_NP);
    pthread_mutex_init(mtx, mtxattr);

    for (i = 0; i < THREAD_NUM; i++)
    {  
        ctx_arr[i].id = i;
        if (pthread_create(&ctx_arr[i].tid, NULL, t_worker,
                (void *)(ctx_arr + i)) != 0)
        {  
            printf("[ERROR] pthread_create failure\n");
        }
    }

    // To join all threads.
    for (i = 0; i < THREAD_NUM; i++)
    {  
        pthread_join(ctx_arr[i].tid, &res);
    }
    printf("[MSG] All threads are joined.\n");

    close(fd);
    free(mtx);
    free(mtxattr);

    return 0;
}


result :

cat result.dat # mutex off
2
1
0
1
0
1
3
0
1
2
3
4
4
4
4
3
4
4
4
4
cat result.dat # mutex on
1
1
1
1
0
0
0
0
2
2
2
2
3
3
3
3
4
4
4
4

Wednesday, October 21, 2009

installing posix man pages

For example, if you can't get man pages for pthread_create,

(Some blogs recommend you to install glibc-doc, but it wouldn't work.)

just do 'sudo apt-get install manpages-posix-dev'

Tuesday, August 18, 2009

Using autocomplete plugin of jQuery with django

1. Download jQuery auto complete plugin here.

2. Unzip it.

3. Copy below files from the unzipped onto your javascript directory.
  • jquery.autocomplete.css
  • jquery.autocomplete.js
  • jquery.bgiframes.min.js
  • jquery.dimensions.js (from jquery.com)
4. Include them.

5. If you want to activate auto complete onto the html tag with id 'qwerty', add following code onto your js code area.

$(document).ready(function(){
  $("#qwerty").autocomplete{
    '/ajax/autocomplete',
    {multiple: true, multipleSeparator: ', '}
  );
});

6. Setup urls.py so that it can handle '/ajax/autocomplete' with ajax_autocomplete module.

7. in views.py, make ajax_autocomplete module like,

def ajax_autocomplete(request):
  if request.GET.has_key('q'):
    # Write your code here (search dictionary or db, etc..)
    # And put it onto res (separated with '\n').
    return HttpResponse(res)
  return HttpResponse()

8. Done. Have fun :)

Tuesday, August 4, 2009

MSN proxy with ssh using Pidgin

If your company blocked MSN port and you have accessable server out of your company, use it as a proxy with ssh -D option.

Set tunnel with '>ssh -D 1234 yourserveraddress.com'.
(You can use any port number instead of 1234.)

Set proxy option in your pidgin.
Host : localhost
Port : 1234

That's all. Have fun.

Monday, August 3, 2009

Using django - Accounts

Django auth system is in django.contrib.auth.
(Included in INSTALLED_APPS as a default)

Login page
You can use default django login handler.

<urls.py>
urlpatterns = patterns('',
  ...
  (r'^login/$', 'django.contrib.auth.views.login')
<eof>

You need to make a template for login page.

<templates/registration/login.html>
 <form methos='post' action='.'>
  <label for='id_username'>Username : </label>
  {{ form.username }}
  <br>
  <label for='id_password'>Password : </label>
  {{ form.password }}
  <br>
  <input type='hidden' name='next' value='/'/>
  <input type='submit' value='Login'/>
 </form>
<eof>

User objects methods

  • is_authenticated()

  • get_full_name()

  • email_user(subject, message, from_email=None)

  • set_password(raw_password)

  • check_password(raw_password)



Logout
<views.py>
from django.http import HttpResponseRedirect
from django.contrib.auth import logout

def logout_page(request):
  logout(request)
  return HttpResponseRedirect('/')
<eof>

<urls.py>
  ...
  (r'^logout/$', logout_page),
  ...
<eof>

Friday, July 10, 2009

Using django - database

Django seldom uses SQL. It uses python class to access DB.

Pros. You can apply one code to various types of DB.

In django database,
- Data type is equal to table.
- Class member is like a table field.

Designing data model

Create data type(table) in ur sampleapp/models.py
<models.py>
from django.db import models

class SampleTable(models.Model):
  fieldA = models.IntegerField(unique=True)
<eof>

Like IntegerField, there are also pre-defined field.
- TextField, DateTimefield, EmailField, URLField, FileField

Creating data type

Check if you activated your application in settings.py.
<settings.py>
INSTALLED_APPS = (
  ...
  'sampleproject.sampleapp',
)
<eof>

Do DB sync
> ./manage.py syncdb

You can see which SQL were generated with,
> ./manage.py sql sampleapp

Note that it generate 'id' field automatically when it makes table.

Using DB Shell

Like we use database console to execute SQLs in terminal,
django also has shell.
> ./manage.py shell
>>> from sampleapp.models import *
>>> record = SampleTable(fieldA=123)
>>> record.fieldA
123
>>> record.save()
>>> records = SampleTable.objects.all()
>>> firstRecord = SampleTable.objects.get(id=1)
>>> record.delete()

Like above, you can create a record, save it into DB, get all records or single one, access by id and delete it.

Note that until you save(), it just remain only in memory.

User data model

'User' data model is already built in.

>>> from django.contrib.auth.models import User
>>> User.objects.all()

User has many attributes like username, email and password.

Thursday, July 9, 2009

Using django - beginning

Installing

> sudo apt-get install python-django

Start project

> django-admin startproject sampleproject

Then, directory sampleproject is created.
You can run test server using manage.py in the directory.
You can set details using setting.py.
You can set urls using urls.py

Setting DB(ex. SQLite)

Edit setting.py.
- Set DATABASE_NAME, DATABASE_ENGINE

Then create DB.
> ./manage.py syncdb
- Chose if you will set superuser account.

Run server

> ./manage.py runserver

Check it with url 'http://localhost:8000/'
(8000 is default.)

Create application

In one project, you can have several web-applications. Django calls it as applications.

Create with commands,
> ./manage.py startapp sampleapp

Directory sampleapp is then generated.

Application is recommended to be designed with MVC model.

In sampleapp directory,
You can define http request handler function in the file views.py

Implementing handler

Example,
<views.py>
from django.http import HttpResponse

def main(request):
  output = "<html>Welcome!</html>"
  return HttpResponse(output)
<eof>

'request' has many informations about current requests.

<urls.py>
from sampleapp.views import *
...
urlpatterns = patterns('',
  (r'^$', main)
)
<eof>

In 'r'^$', r means regular expression, ^ is first of uri and $ stands for the last.
So it means '/' actually. Function main has been set as a handler for '/' above.

Now, check your first page with browser using 'http://localhost:8000/'. Good job!

Regular expressions

You can use expressions like below
. ^ $ * + ? | [a-z] \w \d
\w means alphabet or '_'
\d means one letter digit.

Wednesday, July 8, 2009

Disable firefox disk cache

Enter about:config in search bar.

set browser.cache.disk.enable as "false"

set browser.cache.disk.capacity as "0"

set browser.cache.memory.capacity (ex. 14336 (256M))

Friday, June 19, 2009

Howto install python and mod_python on linux

Installing Python

If you want to install it onto custom directory, for example like /home/you/opt/Python

Get python sources from python.org

Untar it.

./configure --prefix=/home/you/opt/Python --exec-prefix=/home/you/opt/Python --enable-shared

make; make install;

Installing mod_python

Get mod_python from here http://httpd.apache.org/modules/python-download.cgi

Untar it.

./configure --prefix=/home/you/opt/mod_python --exec-prefix=/home/you/opt/mod_python --with-apxs=/home/you/opt/httpd/bin/apxs --with-python=/home/you/opt/Python/bin/python

# because normal 'make install' needs 'su' access,
make; make install_dso; make install_py_lib

add below configurations into httpd.conf

LoadModule python_module modules/mod_python.so

<Directory [absolute path]>
AddHandler mod_python .py
PythonHandler test
PythonDebug On
</Directory>
Restart httpd. It'll works.

Thursday, June 18, 2009

Terminal login message and broadcasting message

Login message : /etc/motd

Broadcast message : use 'wall' command

MySQL Installing DB and setting password

Before starting server, you need to install database to use.

./mysql_install_db --datadir=<path where db is installed> --user=<dbuser>

Now, you can start server.

./bin/mysqld_safe --user=<user> --port=<dbport> --socket=<sockfile path> --datadir=<path where db is installed> &

As a default, root user can login without password on console. This should be changed.

mysql> delete from mysql.user where user = '' ; # disable anonymous login
mysql> set password for 'root'@'localhost' = password('<root password>'); # set root password

Thursday, June 11, 2009

The way to program smartly.

Don't stick to one methodology.

Things should be done as simply as possible.

They don't read documents. Make it simple as possible.

Requirements always changes.

We don't need to fix tools or process. What we should matter is people.

Organize your team less than 10.

Don't draw all detail architecture. Just draw the big picture.

Monday, June 8, 2009

Httpd configuration for including expire times in response http header

  • First, you need to compile httpd using --enable-expires option. Then it's compiled with expire module built-in your httpd binary(You can check it like, > httpd -l).
  • Second, configuration file should include following setting.
    ExpiresActive On
    ExpireByType image/gif A25920000
    ExpireByType image/png A25920000
    ExpireByType image/jpg A25920000
    ExpireByType image/jpeg A25920000
    This means image file has its expire time as 2592000 seconds later (one month from now)
  • Now, you can check whether expire information is included in the response header like below

Httpd(apache) server status using scoreboard

If you're using httpd as a web server, you can check the status of your server using following url.

"http://localhost:/server-status?auto"

You can check how many workers are busy or idle by checking response page

Thursday, June 4, 2009

Asshole Driven Development

Asshole Driven Development : Any team where the biggest jerk makes all the big decisions is asshole driven development.

Friday, May 29, 2009

Good fonts for programming

for linux users,
  • Bitmap Vera Sans Mono Roman (size 9)
for windows users,
  • Andale Mono
Good vim colorscheme
Ps. You need to adjust anti-aliasing or clear-type setting for using them.

Wednesday, May 20, 2009

jquery

Jquery is simple javascript library like dojo or prototype.

Jquery follows MIT, GPL license and we can use it through here

Tuesday, May 19, 2009

Wednesday, April 22, 2009

Connecting Oracle Database with oracle instant client using PHP in Linux

We can connect Oracle db without oracle client if only we can use instant client instead. But this is for 10g or later version of db.

1. You need to have an account for www.oracle.com. Create a new through Register for a free Oracle Web account) .

2. Choose which release of Oracle instant client you need here.

3. Accept license agreement, find the version you need, click to download. (I chose version 10.2 of basic, sdk and sqlplus package. Note that some version may not work in your system.)

4. Install it. You can install it using rpm or manually using .zip files.

5. If you downloaded sqlplus package and running on linux, you can check whether it works properly with command '>export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:[instant client's path]; ./sqlplus --help' in instantclient directory.

6. Now's the time to test db connection using sqlplus. Make text file with the name 'tnsnames.ora' with connect information like below.

YOURNAME =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = XXX.XXX.XXX.XXX)(PORT = XXXX))
)
(CONNECT_DATA =
(SID = XXXXX)
)
)

7. Set environment variables like below.

> export ORACLE_HOME=
> export TNS_ADMIN=

8. > now try to connect with id/pw. Does it work? Fine!

9. To make it work, you should recompile php module with the option like below.

--with-oci8=instantclient,

10. Check whether follow env variables set set properly

export TNS_ADMIN=/yoursystem/yourpath # where tnsnames.ora exists
export NLS_LANG=KOREAN_KOREA.UTF8 # set encoding for your locale

11. Check with . If you can find oci8 section in the information, it's done.

12. Now you can connect to oracle db using php functions. Let's see sample code.


<?

// ORA_SERVER : defined in tnsnames.ora

// Connect

$conn = oci_connect('user', 'pass', 'ORA_SERVER');

if (!$conn) {

    $e = oci_error();

    print htmlentities($e['message']);

    exit;

}

// Execute query

$query  = 'SELECT * FROM SAMPLETABLE';

$stid   = oci_parse($conn, $query);

if (!$stid) {

    $e = oci_error($conn);

    print htmlentities($e['message']);

    exit;

}

$r = oci_execute($stid, OCI_DEFAULT);

if (!$r) {$e = oci_error($stid);

    echo htmlentities($e['message']);

    exit;

}



// Fetch results

while ($row = oci_fetch_array($stid, OCI_RETURN_NULLS)) {

    foreach ($row as $item) {

        print ($item?htmlentities($item):'&nbsp;').'<br>';

    }

}



// Close

oci_close($conn);

?>

Monday, March 2, 2009

XML Document Validation Tool

If that's just for debug and development purpose, simply use xmllint.

> xmllint yourDocument.xml

Tuesday, February 17, 2009

제목 보고 당했다.


"새표준 C C99" C99 표준 번역서 인줄 알고 냉큼 샀는데, C 기초 프로그래밍 책이다.

이런,.

Thursday, February 12, 2009