WindowsとMacの両方でローカル配信メールを設定する方法

ローカルからアプリケーションがメール投げようとすると何も設定してないとエラーになっちゃいますよね。 私の職場じゃWindowsMac使っている人が半々なのですが、どちらの環境でもこのメールの問題を解決したので書いてみます。

Windowsの場合

簡単です。 smtp4devをダウンロードして起動するだけで25ポートをlistenしてくれます。 あとはアプリがメール投げたらこの子がそれを拾ってくれるようになります。

smtp4dev.codeplex.com

Macの場合

Macの場合は、OSがUnix系なので、postfixを設定します。 設定をすると/Users/{username}/Maildir/の配下にメールが届くようになります。 こんな感じ。

# command : create setting file for postfix
$ sudo vi /etc/postfix/aliases.reg

## discription
/(?!^root$|^{username}$)^.*$/ {username}

# command : create setting file for postfix
$ sudo vi /etc/postfix/transport_maps

## discription
/^.*@.*$/ local

# command : modify setting file for postfix
$ sudo vi /etc/postfix/main.cf

## discription
#local_recipient_maps =#local_recipient_maps =
local_recipient_maps =

#alias_maps = netinfo:/aliases#alias_maps = netinfo:/aliases
alias_maps = hash:/etc/postfix/aliases,regexp:/etc/postfix/aliases.reg

#home_mailbox = Mailbox#home_mailbox = Mailbox
home_mailbox = Maildir/

#luser_relay = admin+$local#luser_relay = admin+$local
luser_relay = {username}

# add following description to bottom line
transport_maps = pcre:/etc/postfix/transport_maps

# command : create aliases's binaly file
$ sudo postalias /etc/postfix/aliases

# command : reflect
$ sudo postfix reload

{username}は各自のユーザ名に置き換えてください。

postfixが起動してないとsudo postalias /etc/postfix/aliasesの箇所でエラーになります。

その場合は、sudo postfix startしてあげてください。

参考にしたサイトはこちら。

https://blog.tagbangers.co.jp/ja/2015/05/22/setting-of-local-e-mail-for-mac

http://open-groove.net/postfix/mail-forward/

CentOS7でLAMP環境構築(といいつつnginx)

友人のサイトのローカル環境を構築するのにLAMP環境をMacに入れたので、参考にしたサイトをメモ代わりに貼っておく。

CentOS 7 でLAMP(Nginx+MariaDB(MySQL)+PHP)インストールからWordPressを動かすまで(Nginx編) | レンタルサーバー・自宅サーバー設定・構築のヒント

Dockerエキスパート養成読本が出る!

今通ってる現場で、インフラ再構築ってことで今後はドッカー使うらしい。 ってことで勉強しとかないとついていけないので、電子書籍版出たらすぐ買う。

books.rakuten.co.jp

www.amazon.co.jp

IDEのテーマをSublime Text 3っぽく変更

NetBeans Monokai Sublime

Eclipse qiita.com

テキストエディタにはsublime text3使ってます。 これで、見た目の統一感が出た。気持ちいい!

Javaでprivateメソッドをmockに置き換える方法

探していたら、mockitoでは実装してない旨のページを見つけた。

github.com

powermockでできるからそっち使ってねって書いてある(ように読める)…○r2”

社内のMさんとOさんに助けを求めたら教えてもらえたのだが、 どうやら、jmockitなるものを使えば、簡単にstaticだろうがfinalだろうがprivateだろうがmockに置き換えられる模様。 ↓教えてもらったサイト↓ qiita.com

やり方を具体例で教えてもらったが、確かに簡単かつ意図したとおりにできた!

ので、今後お世話になりそうなのでメモしておく。

以下、サンプルコード

package test.jmockit;

public class JmockitSample {
    public static void main(String[] args){
        System.out.println(new JmockitSample().methodPublic());
        System.out.println(methodPublicStatic());
        System.out.println(new JmockitSample().callPraivateMethod());
        System.out.println(callPrivateStaticMethod());
    }

    public String methodPublic() {
        return "methodPublic";
    }

    public static String methodPublicStatic() {
        return "methodPublicStatic";
    }
    
    public String callPraivateMethod(){
        return new JmockitSample().methodPrivate();
    }
    
    private String methodPrivate() {
        return "methodPrivate";
    }

    public static String callPrivateStaticMethod(){
        return JmockitSample.methodPrivateStatic();
    }
    private static String methodPrivateStatic() {
        return "methodPrivateStatic";
    }
}

実行結果

methodPublic
methodPublicStatic
methodPrivate
methodPrivateStatic

テストコード内で4つのメソッドを前もmockに置き換えて、 Mocked{メソッド名}に変更してみる

package test.jmockit;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import mockit.Mock;
import mockit.MockUp;

import org.junit.Test;

public class JmockitSampleTest {

    @Test
    public void mockTest(){

        new MockUp<JmockitSample>() {

            @Mock
            public String methodPublic() {
                return "mocked methodPublic";
            }

            @Mock
            public String methodPublicStatic() {
                return "mocked methodPublicStatic";
            }

            @Mock
            private String methodPrivate() {
                return "mocked methodPrivate";
            }

            @Mock
            private String methodPrivateStatic() {
                return "mocked methodPrivateStatic";
            }
        };

        System.out.println(new JmockitSample().methodPublic()      );
        System.out.println(JmockitSample.methodPublicStatic()      );
        System.out.println(new JmockitSample().callPraivateMethod());
        System.out.println(JmockitSample.callPrivateStaticMethod() );
        
        assertThat(new JmockitSample().methodPublic(), is("mocked methodPublic"));
        assertThat(JmockitSample.methodPublicStatic(), is("mocked methodPublicStatic"));
        assertThat(new JmockitSample().callPraivateMethod(), is("mocked methodPrivate"));
        assertThat(JmockitSample.callPrivateStaticMethod(), is("mocked methodPrivateStatic"));

    }
}

結果

mocked methodPublic
mocked methodPublicStatic
mocked methodPrivate
mocked methodPrivateStatic

注意点

クラスパスがjunitよりも先に来てないとうまく動かないので、その点だけ注意して下さい。 Eclipseだとこんな感じにすればOK f:id:kogayushi:20150607105459p:plain

gradleならこんな感じ

apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = '1.7'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

configurations {
    jmockit_agent
}

repositories {
    mavenCentral()
}

dependencies {
    jmockit_agent 'com.googlecode.jmockit:jmockit:1.7'
    testCompile 'com.googlecode.jmockit:jmockit:1.7'
    testCompile 'junit:junit:4.11'
    
}

test {
    jvmArgs "-javaagent:${configurations.jmockit_agent.asPath}"
}

ちなみにNetBeansの場合は、gradle使ってる場合はgradleに完全にコンパイル任せてるのでbulid.gradleの設定さえしておけば、うまく動きます。

参考にしたサイトその1 knjname.hateblo.jp

参考にしたサイトその2 gist.github.com

Base64でデコード出来ない文字をデコードしようとするとどうなるか?

調べても出てこなかったので、メモ。

何かわかりやすいエラーが出るのかとおもいきや、エラーとわかりやすい挙動はしなかった…orz

答えは『実装による』っぽい。 実際に平文→base64.decode→sha256でhash→base64.encodeの処理をOpensslとJavaの両方で試したら挙動に違いがった。

例えばOpenSSLだとこうなる

$ echo "aaaa" | openssl base64 -d | openssl sha -sha256 -binary | openssl base64
# u6nbXovptodruQ0AGBFeI/x0G6a/IyXn/PiO/tdQxMc=
$ echo "aaaa,,,," | openssl base64 -d | openssl sha -sha256 -binary | openssl base64
# 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=

aaaaの場合とaaaa,,,,の場合で結果が異なる。 ,base64で使用していない文字なのでデコード出来ない。 そのため、,が入るとエラーになって、その結果毎回同じ文字列が返ってきている(ように見える)。

試しにaaaaaaaa,も試してみる

$ echo "aaaaaaa," | openssl base64 -d | openssl sha -sha256 -binary | openssl base64
# 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=

やっぱり47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=になる。

なぜ47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=が返ってくるかはOpenSSLの中身を見ていないのでわからない。

Apache Commons Codecだとこうなる

package Base64Test;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Base64;

public class Base64TestExecutor {
    public static void main(String[] agrs) throws NoSuchAlgorithmException{
        String target1 = "aaaa";
        String target2 = "aaaa,,,,";
        String result1 = testDigest(target1);
        String result2 = testDigest(target2);
        System.out.println("result1 : " + result1);
        System.out.println("result2 : " + result2);
        System.out.println("isEqual : " + result1.equals(result2));
    }

    private static String testDigest(String targetString) throws NoSuchAlgorithmException {
        
        byte[] testResult = Base64.decodeBase64(targetString);
        
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digestVal = md.digest(testResult);
        String encPassword = Base64.encodeBase64String(digestVal);
        
        return encPassword;
    }
}

実行結果

run:
result1 : u6nbXovptodruQ0AGBFeI/x0G6a/IyXn/PiO/tdQxMc=
result2 : u6nbXovptodruQ0AGBFeI/x0G6a/IyXn/PiO/tdQxMc=
isEqual : true
ビルド成功(合計時間: 0秒)

こっちは中身を見てみた。

    /**
     * <p>
     * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
     * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
     * call is not necessary when decoding, but it doesn't hurt, either.
     * </p>
     * <p>
     * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
     * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
     * garbage-out philosophy: it will not check the provided data for validity.
     * </p>
     * <p>
     * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
     * </p>
     *
     * @param in
     *            byte[] array of ascii data to base64 decode.
     * @param inPos
     *            Position to start reading data from.
     * @param inAvail
     *            Amount of bytes available from input for encoding.
     * @param context
     *            the context to be used
     */
    @Override
    void decode(final byte[] in, int inPos, final int inAvail, final Context context) {
        if (context.eof) {
            return;
        }
        if (inAvail < 0) {
            context.eof = true;
        }
        for (int i = 0; i < inAvail; i++) {
            final byte[] buffer = ensureBufferSize(decodeSize, context);
            final byte b = in[inPos++];
            if (b == pad) {
                // We're done.
                context.eof = true;
                break;
            } else {
                if (b >= 0 && b < DECODE_TABLE.length) {
                    final int result = DECODE_TABLE[b];
                    if (result >= 0) {
                        context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK;
                        context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result;
                        if (context.modulus == 0) {
                            buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS);
                            buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
                            buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS);
                        }
                    }
                }
            }
        }

        // Two forms of EOF as far as base64 decoder is concerned: actual
        // EOF (-1) and first time '=' character is encountered in stream.
        // This approach makes the '=' padding characters completely optional.
        if (context.eof && context.modulus != 0) {
            final byte[] buffer = ensureBufferSize(decodeSize, context);

            // We have some spare bits remaining
            // Output all whole multiples of 8 bits and ignore the rest
            switch (context.modulus) {
//              case 0 : // impossible, as excluded above
                case 1 : // 6 bits - ignore entirely
                    // TODO not currently tested; perhaps it is impossible?
                    break;
                case 2 : // 12 bits = 8 + 4
                    context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits
                    buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
                    break;
                case 3 : // 18 bits = 8 + 8 + 2
                    context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits
                    buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
                    buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
                    break;
                default:
                    throw new IllegalStateException("Impossible modulus "+context.modulus);
            }
        }
    }

final int result = DECODE_TABLE[b]; の箇所でDECODE出来ない文字の場合は-1が返される。 そのため、result >= 0falseになるので、その文字は読み飛ばされる。 その結果、例えばaa,,,の結果はおなじになる。

こういうのは実際に試して動作確認して、中身のソースまで見てみないとわかんないですね!